Webhooks
Receive real-time events when checkout charges are created, paid, or fail.
Checkout webhooks notify your server when a hosted or API-created charge changes state. Register endpoints from your dashboard under Developers → Checkout.
Endpoint scope
Each webhook endpoint is product-scoped. An endpoint registered under Developers → Checkout receives only checkout events — it never receives banking or card events. Each endpoint has its own verification token.
Checkout endpoints can additionally filter by product: subscribe to all products or to a selected set. A product-scoped endpoint only receives events for its selected products.
Event types
| Event | When |
|---|---|
checkout.initiated | A hosted or API-created checkout charge is created. |
checkout.payment.completed | Checkout detects a successful payment (data.status is paid). |
checkout.payment.failed | A PIX checkout reaches a terminal failed or expired state. Never fulfil from this event. |
New checkout endpoints subscribe to all three events. If you have an older endpoint created before checkout.payment.failed existed, its subscription is unchanged — add that event type to the endpoint to start receiving failures. The transaction-status reads report terminal failure regardless of your webhook subscription.
Payload
Every delivery wraps the charge in a common envelope. Branch on data.paymentMethod: PIX uses bank / pix, BRL amounts, and null on-chain fields; stablecoins use crypto with chain and address details. A successful payment always reports data.status of paid, regardless of rail.
This example is a hosted-link payment — the buyer paid through the product’s checkoutUrl, so data.urlParams carries whatever query parameters you appended to the link (such as utm_source):
{
"id": "evt_01HXR9...",
"type": "checkout.payment.completed",
"createdAt": "2026-07-21T12:01:20.000Z",
"livemode": true,
"data": {
"id": "chk_123",
"checkoutProductId": "6a713a92-350a-4cc6-a996-1e55e97d3a36",
"product": { "id": "6a713a92-350a-4cc6-a996-1e55e97d3a36", "slug": "c-8f41a2d9", "name": "Curso Pro" },
"externalId": "order_1048",
"buyerEmail": "ana.silva@example.com",
"amount": "49.90",
"amountReceived": "49.90",
"pricing": null,
"currency": "BRL",
"paymentMethod": "bank",
"paymentRail": "pix",
"status": "paid",
"createdAt": "2026-07-21T12:00:00.000Z",
"updatedAt": "2026-07-21T12:01:20.000Z",
"urlParams": { "userID": "order_1048", "utm_source": "ads" }
}
}
API-created sessions
For a charge created with POST /v1/checkout/sessions, data.externalId is the userId you sent, and data.urlParams always includes your supplied or generated reference as txn_id — match on it to find your order:
{
"id": "evt_01HXRB...",
"type": "checkout.payment.completed",
"createdAt": "2026-07-22T12:01:20.000Z",
"livemode": true,
"data": {
"id": "chk_124",
"externalId": "user_42",
"amount": "39.92",
"status": "paid",
"urlParams": {
"txn_id": "txn_1048",
"userID": "user_42",
"userEmail": "ana.silva@example.com"
}
}
}
…the other data fields are the same as in the hosted example above. For a discounted session, data.amount is the final payable total and data.pricing preserves the original amount, normalized percentage, discount amount, and final amount. Charges without session pricing metadata return pricing: null.
Verifying deliveries
Every request includes these headers:
X-Dolafy-Token— your per-endpoint verification token, shown once when the endpoint is created.Dolafy-Event-Id— unique event id (also the bodyid).Dolafy-Event-Type— the event type.Dolafy-Delivery-Id— unique delivery id; retries of the same delivery reuse it.
Compare X-Dolafy-Token to the token you stored at creation and reject the request if it does not match. There is no HMAC signature — because the token is sent on every request, your endpoint must use HTTPS, keep the token secret, and avoid logging request headers. Rotate the token by recreating the endpoint.
Delivery semantics
- Respond with a
2xxquickly, then process the event asynchronously. checkout.payment.completedis retried up to three total attempts on timeouts, network errors, or5xxresponses (backoff 500ms then 1s), reusing the same event and delivery ids. All other checkout events are single-attempt.- There is no durable retry queue or replay endpoint. Make your handlers idempotent by
Dolafy-Event-Id(orDolafy-Delivery-Id), and reconcile checkout status withGET /v1/checkout/transactions/:transactionReference. - Fulfil only after
checkout.payment.completed; treatcheckout.payment.failedas terminal failure/expiry. - Manual test deliveries sent from the dashboard include top-level
"test": true. Never fulfil an order or move money from a test event.