Skip to main content

Webhooks in Practice

Webhooks tell your server when something changes — a ticket moves, a KYC finishes, a limit is updated. This page explains how delivery behaves so your handler processes each event once and never misses one. Registering a URL is in Notifier Webhooks; every event and payload is in Webhook Events.

What arrives​

Each delivery is a POST to your URL with a JSON body like:

{
"event": {
"id": "00000000-0000-0000-0000-000000000000",
"accountId": "11111111-1111-1111-1111-111111111111",
"subscription": "TICKET",
"data": {
"type": "DELIVERY-SUCCESS",
"ticket": { "id": "...", "status": "PAID", "...": "..." }
},
"createdAt": "2026-10-22T18:06:50.808725Z",
"EventType": ""
}
}
  • event.id identifies the event. It stays the same when the event is delivered again or resent.
  • event.accountId is the account the event is about: the sub-account for sub-account operations, otherwise the main account.
  • data.type is the event name. Names use hyphens: DELIVERY-SUCCESS, TICKET-COMPLETE, DEPOSIT-FAILED.
  • Ticket events carry the whole ticket in data.ticket, with the same fields as GET /v2/account/tickets/{id}.
  • The Signature header lets you check the event came from Avenia — see Verifying Authenticity.

How to handle them​

  1. Answer 2xx fast. Any 2xx counts as received; the body you return is ignored. Store the event and process it afterwards, so you answer within a few seconds.
  2. Deduplicate by event.id. The same event can arrive more than once.
  3. Don't rely on order. Events of one ticket are sent in parallel and can arrive out of order. Decide from the ticket status in the payload (or read the ticket), not from the order of arrival.
  4. Read the final status. TICKET-COMPLETE arrives for success and failure alike. Check data.ticket.status and failureReason.

When your endpoint fails​

  • If your endpoint answers with a non-2xx status, the event is not sent again. It is recorded as an unacknowledged attempt.
  • If your endpoint can't be reached (connection refused, no response), the delivery is retried briefly. If it still fails, no attempt is recorded for that event.

So after an outage on your side, recover what you missed:

  1. List the events created since the outage started: GET /v2/notifications/webhooks/events?createdAfter=<epoch milliseconds> (optionally &subscription=TICKET, and &subAccountId= for a sub-account). Compare their ids with the events you already processed. This finds events whether or not your endpoint ever answered.
  2. For non-2xx answers, GET /v2/notifications/webhooks/attempts?unackedOnly=true lists them — see Webhook Attempts.
  3. Resend a ticket's events: POST /v2/account/tickets/{ticketId}/resend-webhooks (add ?subAccountId= for a sub-account's ticket). It sends all of that ticket's events again with their original event.id; like any delivery, they can arrive in any order. Tickets older than 30 days can't be resent.
  4. Or skip the webhook: read the ticket with GET /v2/account/tickets/{ticketId}.
  5. Mark attempts you've handled with PATCH /v2/notifications/webhooks/attempts.

Questions that come up​

Is there an event when an UNPAID ticket expires? No. The ticket stays UNPAID; see Expired tickets.

I received ticket events for a PIX I never created a ticket for. A PIX sent to your static key or BR Code creates a ticket automatically, with reason Pix deposit without ticket, and sends the usual events. An on-chain deposit to your deposit address does the same with Blockchain deposit without ticket.

How do I tell a pay-in from a payout? There's no direction field. Read data.ticket.quote.inputPaymentMethod and outputPaymentMethod, or which sender/receiver block the ticket has (brazilianFiatSenderInfo for a PIX received, brazilianFiatReceiverInfo for a PIX sent).

Where is the payer's tax ID? In the sender block of the ticket, for example data.ticket.brazilianFiatSenderInfo for a PIX deposit.

Can I register more than one URL? Up to 3 per account, HTTPS only. Each can subscribe to a subset of events or to *.