What’s worth a webhook
Registering an endpoint
POST /webhooks
url must be an absolute https:// endpoint — plain HTTP is rejected with 400.
Authenticating the caller
Your endpoint is public, so it needs a way to tell a real Venly delivery from anyone else who found the URL. Pick one method at registration:API_KEY
Venly injects
apiKey into the header you name in headerName. Compare it against your stored
value using a constant-time comparison.BASIC_AUTHENTICATION
Standard HTTP basic auth with
username and password.Testing your endpoint
Once registered, send a ping:PING event to that one webhook, exercising the real delivery path —
including the authentication header your handler expects.
Delivery is asynchronous. A
200 means the ping was accepted for delivery, not that your endpoint
received it. Confirm arrival in your own logs.Updating is a full replacement
PUT /webhooks/{webhookId} replaces the whole
configuration — it is not a partial patch.
Because secrets are never returned by a read, you cannot fetch the current config, tweak one field,
and send it back: the secret would be missing. Always send every field you want to keep, including a
complete authenticationMethod.
This is also how you rotate a key or password.
What you receive
Every delivery has the same two-field shape — the event type, and an event-specificresult:
No event carries an amount. This is deliberate. Amounts, balances and bank coordinates stay
behind the authenticated REST API — an event tells you what changed, and you read the values back
over REST. So a
PAYOUT_RETURNED tells you a return happened, not how much came back.Every webhook receives every event. Registration has no event-type selection, so you cannot
narrow what an endpoint gets. Switch on
eventType in your handler and ignore what you don’t need.occurredAt is when the state change happened, not when it was delivered — deliveries typically
land one to twenty seconds later, and a retry can arrive much later.
Event catalogue
Thirteen event types:Coming to this catalogue: partner-terms acceptance. An event announcing that an end customer has
completed partner-terms consent is being added, so
you will not have to poll the party to learn that onboarding can proceed. The event type is being
finalised — until it lands, read the party back to check its consent state.
PAYOUT_REJECTEDvsPAYOUT_FAILED— rejected means nothing was ever sent; failed means execution started and then broke. Only the second can leave a pay-out needing investigation.PAYOUT_RETURNEDdoes not imply an earlierPAYOUT_COMPLETED. A return can be reported while the pay-out is still processing, so never treat it as a correction to a completion you must have received.VIRTUAL_BANK_ACCOUNT_CREATEDfires on readiness, not on creation. The resource exists earlier, without usable bank coordinates. This event means deposits will now land.PARTY_VERIFICATION_COMPLETEDfires on every verdict, including a re-verification of the same party. Others are keyed per resource, so a second delivery for the same resource is a duplicate; this one may be genuinely new.
Event payloads
Transfers
Transfers
Crypto and fiat-originated transfers publish the same event with an identical payload — nothing reveals which one it was.
TRANSFER_COMPLETED
TRANSFER_FAILED
reason is one of ON_CHAIN_FAILED, INSUFFICIENT_FUNDS, INSUFFICIENT_ALLOWANCE, NOT_SUBMITTED, UNKNOWN.A failed transfer has no transactionHash when it never reached the chain (NOT_SUBMITTED).Pay-outs
Pay-outs
A pay-out publishes up to two events: one when it reaches the banking partner, and one for its outcome.
PAYOUT_PROCESSING
PAYOUT_COMPLETED
PAYOUT_RETURNED
PAYOUT_REJECTED and PAYOUT_FAILED have the same shape as PAYOUT_RETURNED, with status REJECTED or FAILED.reason is one of REJECTED_BY_PROVIDER, ROUTE_UNAVAILABLE, SEND_FAILED, RETURNED_BY_BANK, UNKNOWN.The
reason is a fixed enum, never free text. Operational notes recorded against a pay-out internally are not delivered — if you need more detail than the enum, read the pay-out back.Pay-ins
Pay-ins
PAY_IN_SETTLED
PAY_IN_FAILED
reason is one of SETTLEMENT_FAILED, REJECTED_BY_PROVIDER, UNKNOWN.A PAY_IN_FAILED is terminal for that pay-in — no PAY_IN_SETTLED will follow it.Onboarding
Onboarding
ACCOUNT_WALLETS_PROVISIONED
PARTY_VERIFICATION_COMPLETED
status is VERIFIED, REJECTED, or VERIFICATION_PENDING. The verdict and nothing else — no reasons, documents, or personal data. A rejection tells you the outcome, not why.VIRTUAL_BANK_ACCOUNT_CREATED
Ping
Ping
PING
2xx — it is how you prove your endpoint works.Writing a handler
A few rules that hold regardless of which events you subscribe to:1
Verify before you trust
Check the authentication header first and reject anything that fails, before parsing the body.
2
Respond fast, work later
Acknowledge with a
2xx immediately and hand the payload to a queue. Slow handlers look like
failures to any delivery system.3
Expect duplicates
Treat delivery as at-least-once — retries do happen in practice. Key your processing on the
event’s resource id plus target status so a repeat is a no-op — for a second delivery of
TRANSFER_COMPLETED, the transferId is already COMPLETED on your side and there is nothing
to do. Same discipline as idempotency on the request side.4
Don't assume ordering
A later state can arrive before an earlier one, so compare against the state you already hold.
5
Re-read the resource for anything that matters
For money movement, treat the event as a hint that something changed and confirm against the API
— for example Get a payout — before crediting a
ledger or releasing goods.
Managing registrations
Registering requires the
manage:webhooks role; reading requires view:webhooks.
There is a per-company cap on registered webhooks — exceeding it returns 409. If you need more
endpoints than the cap allows, fan out on your own side from a single receiver.
Webhook delivery is handled by Venly. A
503 on any of these endpoints means
that service was briefly unreachable, not that your registration is broken — retry with backoff.Next steps
Pay-outs
Where webhooks matter most — a
201 isn’t a success signal.Idempotency
The request-side counterpart to duplicate-tolerant handlers.

