POST requests with a Content-Type: application/json header delivered to your configured endpoint URL.* to receive everything.| Convention | Detail |
|---|---|
| Format | JSON |
| HTTP Method | POST |
| Content-Type | application/json |
| Field naming | camelCase on every event |
| Null fields | Included as null (not omitted) |
| Timestamps | ISO 8601 — YYYY-MM-DDTHH:mm:ssZ |
| Amounts | Decimal string or number in the currency's minor/major unit as configured |
| Card numbers | Masked — last four digits only. Full PAN is never sent |
data changes shape between categories.{
"id": "evt_371ef0989512474abb2889da89417d36",
"type": "card.approved",
"createdAt": "2026-08-22T14:12:59.8432749Z",
"data": { }
}| Field | Type | Description |
|---|---|---|
id | string | Unique event id, prefixed evt_. Use it to deduplicate — a redelivered event keeps the same id |
type | string | The event name. Identical to the X-Event-Type header |
createdAt | string | ISO 8601 UTC timestamp the event was created |
data | object | Event payload. Shape depends on the category |
| Header | Example | Description |
|---|---|---|
Content-Type | application/json | Always JSON |
X-Webhook-Id | wh_9f2c1a4b6d8e4f0a9b3c5d7e1f2a3b4c | The subscription this was delivered for |
X-Webhook-Signature | 4a7f2c8e91b0… | Lowercase hex HMAC-SHA256. See below |
X-Webhook-Timestamp | 1787654021 | Unix epoch seconds. Part of the signed string |
X-Event-Type | card.freeze | The event name, duplicated from the payload's type |
HMAC-SHA256( secret, "{X-Webhook-Timestamp}.{rawBody}" )X-Webhook-Signature using a constant-time comparison. The secret is the one issued when your subscription was created.Sign the body exactly as received. Parsing the JSON and re-serialising it changes whitespace and key order, and the signature will not match.
This is not the REST API's signing scheme. The API uses X-Signature/X-Timestamp/X-Nonce/X-Body-Hashwith a different secret, a different signed string and a different encoding. Verifying a webhook with the API-request scheme can never succeed.
| Rule | Detail |
|---|---|
| Success | Any 2xx. Return it as soon as the event is stored — do your processing afterwards |
| Timeout | 30 seconds. A slower response counts as a failure |
| Retries | 5 attempts: after 1 min, 5 min, 30 min, 2 hours, then 24 hours |
| Exhausted | After the fifth attempt the event is marked Failed and never retried |
| Auto-disable | 50 consecutive failures deactivates your subscription |
| Ordering | Events are not ordered and may arrive more than once |
Make your handler idempotent. Key on the envelope's idand treat an already-applied state as a no-op. A retriedtransaction.settledmust not book twice.
| # | Category | Events |
|---|---|---|
| 1 | Cardholder Application | cardholder.reviewing , cardholder.needinformation , cardholder.approved , cardholder.rejected |
| 2 | Card Status | card.created , card.under.review , card.approved , card.rejected |
| 3 | Card Action | card.activate , card.freeze , card.unfreeze , card.cancel , card.replaced , card.set.pin , card.limit.updated , card.3DS , card.resend.code |
| 4 | Card Top-up | card.topup.completed , card.topup.failed |
| 5 | Transaction | transaction.authorized , transaction.settled , transaction.declined , transaction.reversed , transaction.refunded , transaction.maintainfee , transaction.overdraft |
cardholder.reviewing · cardholder.needinformation · cardholder.approved · cardholder.rejected{
"id": "evt_1ef4af8115c84926a0928f43874fe418",
"type": "cardholder.approved",
"createdAt": "2026-08-22T10:22:39.5378959Z",
"data": {
"customerId": "a636169d-8d5a-4461-b622-82a83df8d037",
"firstName": "Sabrina",
"lastName": "Marie",
"email": "sabrina.marie@example.com",
"applicationStatus": "cardholder.approved",
"applicationReason": "Approved"
}
}| Field | Type | Description |
|---|---|---|
customerId | string | UUID of the customer the application belongs to |
firstName | string | Applicant's first name |
lastName | string | Applicant's last name |
email | string | Applicant's email |
applicationStatus | string | The resulting status. Echoes the event name |
applicationReason | string | Human-readable reason. Carries the rejection or need-information detail |
type | Meaning | Action required |
|---|---|---|
cardholder.reviewing | Under review | Show the applicant it is in progress |
cardholder.needinformation | More information required | Read applicationReason, then call Resubmit KYC/KYB Applicant |
cardholder.approved | Approved | The card can now proceed |
cardholder.rejected | Rejected | Read applicationReason |
{
"id": "evt_5c81a0d33b7e4d9fa2e6b4c8d0f13579",
"type": "cardholder.needinformation",
"createdAt": "2026-08-22T10:20:11.4471002Z",
"data": {
"customerId": "a636169d-8d5a-4461-b622-82a83df8d037",
"firstName": "Sabrina",
"lastName": "Marie",
"email": "sabrina.marie@example.com",
"applicationStatus": "cardholder.needinformation",
"applicationReason": "Proof of address is unreadable. Please upload a clearer copy."
}
}card.created · card.under.review · card.approved · card.rejected{
"id": "evt_371ef0989512474abb2889da89417d36",
"type": "card.approved",
"createdAt": "2026-08-22T14:12:59.8432749Z",
"data": {
"customerId": "3a8c0d54-7c83-4b69-9cc4-e234a6cd41ec",
"cardId": "924df277-82ad-49e3-b57e-30db104e74f8",
"cardType": "Virtual",
"cardNumber": "XXXX XXXX XXXX 9315",
"taskId": null,
"applicationStatus": "card.approved",
"applicationReason": "Success"
}
}| Field | Type | Description |
|---|---|---|
customerId | string | UUID of the card owner. For an employee card this is the company, not the employee |
cardId | string | UUID of the card |
cardType | string | Virtual or Physical |
cardNumber | string | Masked PAN, last four visible. null on card.created and card.under.review, because the issuer has not returned a number yet. Populated from card.approved onward |
taskId | string | Present only when an operation triggered the status change. null on a normal issuance |
applicationStatus | string | The resulting status. Echoes the event name |
applicationReason | string | Human-readable reason |
{
"id": "evt_84f0b1c6e9d24a7b8c3f5e0a1d269473",
"type": "card.rejected",
"createdAt": "2026-08-22T14:31:08.2260517Z",
"data": {
"customerId": "3a8c0d54-7c83-4b69-9cc4-e234a6cd41ec",
"cardId": "924df277-82ad-49e3-b57e-30db104e74f8",
"cardType": "Virtual",
"cardNumber": null,
"taskId": null,
"applicationStatus": "card.rejected",
"applicationReason": "Issuer declined the application"
}
}card.activate · card.freeze · card.unfreeze · card.cancel · card.replaced · card.set.pin · card.limit.updated · card.3DS · card.resend.codetaskId.{
"id": "evt_123c649c9e674a9ab89c2f4533dfc983",
"type": "card.freeze",
"createdAt": "2026-08-22T10:17:58.0661445Z",
"data": {
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"cardId": "4dd662bb-3730-4459-b905-402d449b62ce",
"cardType": "Virtual",
"cardNumber": null,
"taskId": "9eeee0e6-0c35-4af7-912a-ef865e70ee9f",
"applicationStatus": "Frozen",
"applicationReason": "Card frozen successfully."
}
}| Field | Type | Description |
|---|---|---|
customerId | string | UUID of the card owner |
cardId | string | UUID of the card. This identifies the card — use it, not cardNumber |
cardType | string | Virtual or Physical |
cardNumber | string | Masked PAN. null on every action except card.replaced — see the note below |
taskId | string | The id returned by the REST call that started this operation. Use it to match the event to your request |
applicationStatus | string | The resulting state — see Status Values |
applicationReason | string | Human-readable message |
Why cardNumberisnullhere. These events report a state change on a card you already know about — freezing it does not change its number, so none is sent. The one exception iscard.replaced, which mints a new card and therefore carries the new masked PAN. Identify the card bycardIdin every case.
card.replaced — carries the new card number{
"id": "evt_6b0d29e7c1f34a58a9d3e5b7c2408f16",
"type": "card.replaced",
"createdAt": "2026-08-22T11:02:33.7714920Z",
"data": {
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"cardId": "7f1a44cd-9b02-4e77-8c15-6ad3e0b91742",
"cardType": "Physical",
"cardNumber": "XXXX XXXX XXXX 4471",
"taskId": "c31b7e90-52aa-4f18-90d7-1e6b8a2f4c05",
"applicationStatus": "Replacement Requested",
"applicationReason": "Card replacement requested."
}
}card.resend.code — adds two fields| Field | Type | Description |
|---|---|---|
activationCode | string | The activation code |
activationCodeExpiresAt | string | ISO 8601 UTC expiry, when the issuer supplies one |
The activation code is delivered only on this event. It is never returned on the REST response. Without handling this event you cannot complete a physical card activation.
{
"id": "evt_e40a7c92b6d84f1cae35d9f087b21460",
"type": "card.resend.code",
"createdAt": "2026-08-22T11:40:05.3392118Z",
"data": {
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"cardId": "4dd662bb-3730-4459-b905-402d449b62ce",
"cardType": "Physical",
"cardNumber": null,
"taskId": "1d90cf62-77b4-4a03-9c58-b0e14f5a2d38",
"applicationStatus": "Active",
"applicationReason": "Activation code resent successfully.",
"activationCode": "483920",
"activationCodeExpiresAt": "2026-08-22T12:10:05Z"
}
}card.topup.completed · card.topup.failed{
"id": "evt_9c4e17a05b8d43f2b71c6ade90f3527b",
"type": "card.topup.completed",
"createdAt": "2026-08-22T13:05:44.6620391Z",
"data": {
"transactionId": "0f2a6d31-88ce-4b17-9a05-3d7f2e4c8b60",
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"cardId": "7c290ef9-9d3f-4ef0-bb54-831ddc336a54",
"taskId": "TX20260822130544",
"amount": 250.00,
"currency": "USDT",
"status": "SUCCESS",
"remarks": "Card top-up completed"
}
}| Field | Type | Description |
|---|---|---|
transactionId | string | UUID of the top-up transaction |
customerId | string | UUID of the card owner |
cardId | string | The card at the issuer |
taskId | string | Correlates with the top-up request |
amount | number | Amount credited |
currency | string | Currency of the amount |
status | string | SUCCESS or FAILED |
remarks | string | Human-readable detail. Carries the failure reason on card.topup.failed |
{
"id": "evt_3d70b8f1a5c94e26b09e4c7d1f83a052",
"type": "card.topup.failed",
"createdAt": "2026-08-22T13:09:12.8830476Z",
"data": {
"transactionId": "9b4e17c2-60da-4f83-a715-2e8c05d3f194",
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"cardId": "7c290ef9-9d3f-4ef0-bb54-831ddc336a54",
"taskId": "TX20260822130912",
"amount": 250.00,
"currency": "USDT",
"status": "FAILED",
"remarks": "Insufficient wallet balance"
}
}transaction.authorized · transaction.settled · transaction.declined · transaction.reversed · transaction.refunded · transaction.maintainfee · transaction.overdraft{
"id": "evt_2fb85a0c7d1e44639ac0b3e51d78f204",
"type": "transaction.settled",
"createdAt": "2026-08-22T15:48:20.5514773Z",
"data": {
"event": "settlement",
"transactionId": "b5c81f04-3a92-4d17-90e6-7c2f5a8b1d43",
"cardId": "7c290ef9-9d3f-4ef0-bb54-831ddc336a54",
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"originalTransactionId": null,
"localCurrency": "GBP",
"localAmount": "42.50",
"authorizedAmount": "53.10",
"authorizedCurrency": "USD",
"authorizationCode": "AUTH004512",
"authorizedAt": "2026-08-22T15:41:02Z",
"fee": "0.30",
"feeCurrency": "USD",
"crossborderFee": "0.53",
"crossborderFeeCurrency": "USD",
"settleAmount": "53.93",
"settleCurrency": "USD",
"settleDate": "2026-08-23",
"merchantName": "Deliveroo",
"merchantCategoryCode": "5812",
"merchantCountry": "GB",
"merchantCity": "London",
"type": "settlement",
"subType": "settlement",
"status": "SETTLED",
"description": "Card purchase"
}
}| Field | Type | Description |
|---|---|---|
event | string | The transaction lifecycle action |
transactionId | string | UUID of this transaction |
cardId | string | The card at the issuer |
customerId | string | UUID of the card owner |
originalTransactionId | string | The transaction being refunded or reversed. null on an original transaction |
localCurrency | string | ISO 4217 currency code of the transaction at the point of sale |
localAmount | string | Amount in the local currency |
authorizedAmount | string | Amount authorized in the card's billing currency |
authorizedCurrency | string | ISO 4217 code of the card's billing/wallet currency |
authorizationCode | string | Authorization code returned by the network |
authorizedAt | string | ISO 8601 timestamp when authorization was issued |
fee | string | Platform or card fee applied |
feeCurrency | string | Currency of the fee |
crossborderFee | string | Cross-border/FX conversion fee, if applicable |
crossborderFeeCurrency | string | Currency of the cross-border fee |
settleAmount | string | Pre-settlement amount |
settleCurrency | string | Currency of the settlement amount |
settleDate | string | ISO 8601 expected or actual settlement date |
merchantName | string | Name of the merchant |
merchantCategoryCode | string | ISO 18245 MCC code |
merchantCountry | string | ISO 3166-1 alpha-2 merchant country code |
merchantCity | string | Merchant city |
type | string | Transaction type |
subType | string | Transaction sub-type |
status | string | Transaction status |
description | string | Free-text remarks |
originalTransactionId pointing at the transaction being refunded.{
"id": "evt_7a1c9e04d3b8452fae06c92d14f7b385",
"type": "transaction.refunded",
"createdAt": "2026-08-23T09:12:41.2049167Z",
"data": {
"event": "refund",
"transactionId": "e40c9a72-1f68-4b35-8d02-9c7a5e13f860",
"cardId": "7c290ef9-9d3f-4ef0-bb54-831ddc336a54",
"customerId": "ee22ee94-0523-4b7b-8426-4f5589f4a56f",
"originalTransactionId": "b5c81f04-3a92-4d17-90e6-7c2f5a8b1d43",
"localCurrency": "GBP",
"localAmount": "42.50",
"authorizedAmount": "53.10",
"authorizedCurrency": "USD",
"authorizationCode": "AUTH004512",
"authorizedAt": "2026-08-23T09:12:38Z",
"fee": "0.00",
"feeCurrency": "USD",
"crossborderFee": "0.00",
"crossborderFeeCurrency": "USD",
"settleAmount": "53.10",
"settleCurrency": "USD",
"settleDate": "2026-08-24",
"merchantName": "Deliveroo",
"merchantCategoryCode": "5812",
"merchantCountry": "GB",
"merchantCity": "London",
"type": "refund",
"subType": "refund",
"status": "REFUNDED",
"description": "Order cancelled by merchant"
}
}applicationStatus, per event.| Event | applicationStatus |
|---|---|
card.activate | Active |
card.freeze | Frozen |
card.unfreeze | Active |
card.cancel | pending, then success on the follow-up event |
card.replaced | Replacement Requested |
card.set.pin | PIN Set or PIN Changed |
card.limit.updated | Limit Updated |
card.3DS | Code Sent |
card.resend.code | The card's current state |
card.approved · card.rejected · card.under.review | Echoes the event name |
cardholder.* | Echoes the event name |
applicationReason always carries the human-readable detail — read it on any rejection or need-information event.4xx or 5xx from your endpoint, or no response within 30 seconds, is a failed delivery. The payload is not altered between attempts — the envelope id stays the same, which is what makes deduplication reliable.| Your response | Result |
|---|---|
2xx | Delivered. No retry |
4xx / 5xx | Retried on the schedule above |
| Timeout (>30s) | Retried on the schedule above |
| 5 failed attempts | Event marked Failed. Not retried |
| 50 consecutive failures | Subscription deactivated |