API Reference

Webhooks

Receive real-time events when checkout charges are created, underpaid, 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.underpaid A valid stablecoin deposit was received, but the cumulative amount is still below the charge amount. Never fulfil from this event.
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 four events. Existing Checkout endpoints were automatically subscribed to checkout.payment.underpaid during its rollout without changing their product filters. If you have an older endpoint created before checkout.payment.failed existed, add that event type to start receiving PIX 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.

For checkout.payment.underpaid, data.amountReceived is cumulative, data.amountRemaining is the unpaid remainder, and data.receipt identifies the individual deposit. Each partial deposit gets its own event. A deposit that brings the cumulative total to or above the charge amount emits checkout.payment.completed instead.

{
  "id": "evt_01HXRU...",
  "type": "checkout.payment.underpaid",
  "createdAt": "2026-08-26T17:44:42.000Z",
  "livemode": true,
  "data": {
    "id": "chk_125",
    "amount": "3.400000",
    "amountReceived": "3.100000",
    "amountRemaining": "0.300000",
    "paymentMethod": "crypto",
    "settlementCurrency": "USDC",
    "chain": "solana",
    "status": "underpaid",
    "receipt": {
      "amount": "3.100000",
      "currency": "USDC",
      "chain": "solana",
      "txSignature": "2EysQdNzKJXw...",
      "receivedAt": "2026-08-26T17:44:40.000Z"
    }
  }
}

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": "37.45",
    "pricing": {
      "originalAmount": "29.00",
      "discountPercent": null,
      "discountAmount": "0.00",
      "finalAmount": "37.45",
      "currency": "USD"
    },
    "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 an API-created session, data.amount is the final payable total and data.pricing preserves the original catalog amount and final amount. Discounted pricing also includes its normalized percentage and discount 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 body id).
  • 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 2xx quickly, then process the event asynchronously.
  • checkout.payment.completed is retried up to three total attempts on timeouts, network errors, or 5xx responses (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 (or Dolafy-Delivery-Id), and reconcile checkout status with GET /v1/checkout/transactions/:transactionReference.
  • Fulfil only after checkout.payment.completed; treat checkout.payment.underpaid as informational and checkout.payment.failed as 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.