Skip to main content
Much of what the Finance API does finishes after your request returns. A pay-in session converts minutes later. A verification verdict arrives when a reviewer gets to it. A pay-out’s fiat leg settles on the bank’s schedule — and can bounce afterwards. You can poll for all of it, but webhooks are the better answer: register an HTTPS endpoint once, and Venly pushes each state change to you.

What’s worth a webhook

Registering an endpoint

POST /webhooks
The 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.
Secrets (apiKey, password) are write-only. They are accepted on registration and update, and never returned by any read. Store your own copy — if you lose it, you must update the webhook with a new secret rather than recovering the old one.

Testing your endpoint

Once registered, send a ping:
This queues a synthetic 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-specific result:
Two things to build around:
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.
Four of these need care:
  • PAYOUT_REJECTED vs PAYOUT_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_RETURNED does not imply an earlier PAYOUT_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_CREATED fires on readiness, not on creation. The resource exists earlier, without usable bank coordinates. This event means deposits will now land.
  • PARTY_VERIFICATION_COMPLETED fires 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

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).
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_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.
ACCOUNT_WALLETS_PROVISIONED
This is the one event that carries on-chain addresses — they are the point of it. One event per wallet pair, not per account.
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
The bank coordinates are not in the event. Read them from Get a virtual bank account once this arrives.
PING
Sent only by the ping endpoint. Handle it as a no-op, but do return 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.
Do not simply discard anything that looks like a step backwards. Some transitions legitimately reverse an outcome — a pay-out can go COMPLETEDRETURNED when the receiving bank sends it back, sometimes long afterwards. Decide per event type which transitions are real, rather than ordering states and dropping everything below the high-water mark.
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.