Webhooks
Goal: receive near-real-time updates about card activity through encrypted webhook deliveries.
Payloads are subprovider-agnostic and always include a type field.
Quick summary
Subscription endpoints
Subscribe, unsubscribe, and list subscriptions under /api/v1/wh/*.
Encrypted delivery
Webhook deliveries are encrypted and sent with the API-KEY header.
200 + success body
Return HTTP 200 with { "success": true } to stop retries.
Card-scoped
You only receive events for cards created under your API key.
Workflow steps
Create a subscription
POST your callback URL to /api/v1/wh/subscribe.
Receive encrypted deliveries
We POST an encrypted payload to your endpoint with the API-KEY header.
Decrypt and acknowledge
Decrypt the payload and reply with HTTP 200 and { "success": true }. This response is not encrypted.
Success response
Return plain JSON (not encrypted).
Field Type Required Description successboolean Required Must be true to stop retries. JSON{ "success": true }Handle retries and duplicates
Deliveries are at-least-once. Deduplicate by type and timestamp.
Subscription endpoints
All subscription endpoints are encrypted like other /api/v1/* requests. See Encrypted Payload.
- POST
/api/v1/wh/subscribeCreate a webhook subscription for your URL. - POST
/api/v1/wh/unsubscribeDeactivate a subscription by id. - POST
/api/v1/wh/subscriptionsList active subscriptions.
- Subscriptions only receive events created after the subscription is created.
- We do not backfill older events.
- You can register multiple subscriptions per API key (the same event is delivered to each).
Delivery request
| Field | Details |
|---|---|
| Method | POST |
| Header | API-KEY: <your project API key> |
| Body | { "encrypted": "<base64 string>" } |
Delivery envelope schema
| Field | Type | Required | Description |
|---|---|---|---|
encrypted | string | Required | Base64-encoded encrypted payload. |
{
"encrypted": "<base64 string>"
}Retry behavior
| Item | Details |
|---|---|
| Retry trigger | Any response that is not HTTP 200 with { "success": true }. |
| Cadence | Retries are periodic (about once per minute). |
| Guarantee | Deliveries are at-least-once; duplicates are possible. |
Event types
All event payloads include a type field and are provider-agnostic.
| Type | When sent | Notes |
|---|---|---|
card_transaction | Purchase authorizations for specific status/type combinations. | From subproviders. |
card_topup | Any card topup result. | order_id is included when the webhook maps to an order. |
card_status_change | When a card status changes. | Only the end state is delivered. |
card_otp | OTP delivery event. | Contains the OTP code and send timestamp. |
master_account_topup | When an incoming master wallet deposit is detected and credited. | No card_id. Idempotency is based on the deposit transaction hash. |
1. Card transaction
card_transactionOnly for purchase authorizations. We send webhooks for these status/type pairs:
| Status | Type | Meaning |
|---|---|---|
pending | auth | Authorization pending. |
approved | auth | Authorization approved. |
declined | auth | Authorization declined. |
approved | reversal | Authorization reversed. |
approved | fee | Fee posted. |
approved | refund | Refund posted. |
approved | other | Other approved adjustment. |
other | refund | Refund with provider-specific status. |
The tx_type value is derived from provider status/type mappings.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | Event type. Enum: card_transaction |
tx_type | string | Required | Provider-derived transaction type (e.g., transaction_created_auth_pending). |
card_id | string | Required | Card identifier. |
tx_at | stringdate-time | Required | Transaction time (ISO 8601). |
card_tx_data | object | Required | Normalized transaction fields. |
{
"type": "card_transaction",
"tx_type": "transaction_created_auth_pending",
"card_id": "697c9b7559fad5ba001068ce",
"tx_at": "2026-02-03T22:37:11.597606+00:00",
"card_tx_data": {
"auth_type": "purchase",
"status": "pending",
"type": "auth",
"card_label": "3691",
"merchant": {
"name": "GOOGLE *TEMPORARY HOLD",
"mcc": "5734"
},
"fee_amount": 0.25,
"fee_currency": "usd",
"card_currency": "usd",
"card_amount": 99.0,
"merchant_currency": "hkd",
"merchant_amount": 771.2,
"failure_reason": "Card Blocked"
}
}2. Card topup
card_topupSent for every card topup result.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | Event type. Enum: card_topup |
card_id | string | Required | Card identifier. |
tx_at | stringdate-time | Required | Topup time (ISO 8601). |
currency | string | Required | Topup currency (ISO 4217). |
received_amount | string | Required | Amount received (decimal string). |
order_id | string | Optional | Associated order id, when available. |
{
"type": "card_topup",
"card_id": "697c9b7559fad5ba001068ce",
"tx_at": "2026-02-03T22:37:11.597606+00:00",
"currency": "usd",
"received_amount": "30.00",
"order_id": "69827898db842395ebd4821d"
}3. Card status change
card_status_changeWe normalize provider status as in the freeze/unfreeze endpoints. Only the end state is delivered.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | Event type. Enum: card_status_change |
card_id | string | Required | Card identifier. |
changed_at | stringdate-time | Required | Change time (ISO 8601). |
new_status | string | Required | Normalized card status. Enum: pending_activation, active, frozen, closed |
{
"type": "card_status_change",
"card_id": "697c9b7559fad5ba001068ce",
"changed_at": "2026-02-03T22:37:11.597606+00:00",
"new_status": "frozen"
}4. Card OTP
card_otpOTP payloads include the code and the time it was sent.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | Event type. Enum: card_otp |
card_id | string | Required | Card identifier. |
code | string | Required | One-time password. |
sent_at | stringdate-time | Required | OTP send time (ISO 8601). |
{
"type": "card_otp",
"card_id": "697c9b7559fad5ba001068ce",
"code": "687524",
"sent_at": "2026-02-03T22:37:11.597606+00:00"
}5. Master account topup
master_account_topupSent when an incoming master wallet deposit is detected and credited.
- No
card_id(this is a master account balance event). - Idempotency is based on the deposit transaction hash.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | Event type. Enum: master_account_topup |
status | string | Required | Deposit status. Enum: completed |
currency | string | Required | Master account currency. |
master_account_balance | object | Required | Master account balance before and after credit. |
tx_info | object | Required | Blockchain transaction details. |
credited_at | stringdate-time | Required | Credit time (ISO 8601). |
{
"type": "master_account_topup",
"status": "completed",
"currency": "usdt",
"master_account_balance": {
"before": "100.00000000000000",
"after": "150.00000000000000"
},
"tx_info": {
"network": "tron",
"token": "usdt",
"address": "T...",
"txid": "0x...",
"amount_crypto": "50.00000000000000"
},
"credited_at": "2026-02-03T22:37:11.597606+00:00"
}Idempotency and duplicates
- We avoid duplicate inserts using internal idempotency keys, but delivery is at-least-once.
- Use a deterministic key such as
type + timestampfor deduplication. - Always respond quickly with
{"success": true}once your handler has accepted the event.