Skip to content
LogoLogo

Checkout Webhook Events

Status notifications for your checkouts. Register a CHECKOUT endpoint from the dashboard (Devtools → Webhooks).

Registration, signature verification, retries, and URL rules: Webhooks.

Events

EventFires when
checkout.updatedA checkout reaches COMPLETED or FAILED

FAILED covers both failure and expiry (20 minutes). CANCELLED and REJECTED do not fire; reconcile those outcomes with Get Checkout.

Payload

{
  "eventId": "checkout.updated:8f14e45f-ceea-467f-a830-5e3e3c7e2b8a:COMPLETED",
  "event": "checkout.updated",
  "webhookType": "CHECKOUT",
  "timestamp": "2026-02-03T15:05:30.000Z",
  "data": {
    "checkoutId": "8f14e45f-ceea-467f-a830-5e3e3c7e2b8a",
    "status": "COMPLETED",
    "statusReason": null,
    "amount": "15000",
    "fee": "0",
    "netAmount": "15000",
    "tokenId": "550e8400-e29b-41d4-a716-446655440000",
    "txHash": "0x5d53558791c9346d644d077354420f9a93600acf54eb806ecb9aad077c103ee3"
  }
}

The outer fields are the shared envelope. data carries:

FieldTypeDescription
checkoutIdstring (UUID)Matches id from GET /v1/checkout/{id}
statusstringCOMPLETED or FAILED
statusReasonstring | nullStable key when status is FAILED, otherwise null
amountstringAmount in sen
feestringFee in sen. Always "0" on a BLOX_ACCOUNT checkout, and on an FPX checkout unless BLOX has configured a checkout fee for your account — see Fees
netAmountstringWhat settles on-chain. Credit the customer against this, not amount — under DEDUCT_FROM_AMOUNT they differ, and a CHARGE_TO_PREFUND checkout falls back to deducting if your prefund cannot cover the fee
tokenIdstring (UUID)Token delivered — same ids as GET /v1/wallet/networks
txHashstring | nullThe on-chain transfer, once one exists

Amounts are strings in sen, matching the REST response.

Handling

// eventId is unique per (checkout, status) — a repeat is a redelivery.
if (await seen(payload.eventId)) return respond(200);
 
if (payload.data.status === "COMPLETED") {
  // fulfill must also be idempotent on checkoutId.
  await fulfill(payload.data.checkoutId, payload.data.netAmount);
}
 
await record(payload.eventId);
return respond(200);
  • Acknowledge with 2xx within 10 seconds; do the work asynchronously
  • Key idempotency on eventId
  • Make fulfillment idempotent on checkoutId so a crash between fulfillment and recording the event cannot credit twice
  • Treat GET /v1/checkout/{id} as the source of truth for fulfillment