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

  1. Create a subscription

    POST your callback URL to /api/v1/wh/subscribe.

  2. Receive encrypted deliveries

    We POST an encrypted payload to your endpoint with the API-KEY header.

  3. 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).

    FieldTypeRequiredDescription
    success
    booleanRequiredMust be true to stop retries.
    JSON
    {
      "success": true
    }
  4. 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

FieldDetails
MethodPOST
HeaderAPI-KEY: <your project API key>
Body{ "encrypted": "<base64 string>" }

Delivery envelope schema

FieldTypeRequiredDescription
encrypted
stringRequiredBase64-encoded encrypted payload.
JSON
{
  "encrypted": "<base64 string>"
}

Retry behavior

ItemDetails
Retry triggerAny response that is not HTTP 200 with { "success": true }.
CadenceRetries are periodic (about once per minute).
GuaranteeDeliveries are at-least-once; duplicates are possible.

Event types

All event payloads include a type field and are provider-agnostic.

TypeWhen sentNotes
card_transactionPurchase authorizations for specific status/type combinations.From subproviders.
card_topupAny card topup result.order_id is included when the webhook maps to an order.
card_status_changeWhen a card status changes.Only the end state is delivered.
card_otpOTP delivery event.Contains the OTP code and send timestamp.
master_account_topupWhen an incoming master wallet deposit is detected and credited.No card_id. Idempotency is based on the deposit transaction hash.

1. Card transaction

card_transaction

Only for purchase authorizations. We send webhooks for these status/type pairs:

StatusTypeMeaning
pendingauthAuthorization pending.
approvedauthAuthorization approved.
declinedauthAuthorization declined.
approvedreversalAuthorization reversed.
approvedfeeFee posted.
approvedrefundRefund posted.
approvedotherOther approved adjustment.
otherrefundRefund with provider-specific status.

The tx_type value is derived from provider status/type mappings.

FieldTypeRequiredDescription
type
stringRequiredEvent type. Enum: card_transaction
tx_type
stringRequiredProvider-derived transaction type (e.g., transaction_created_auth_pending).
card_id
stringRequiredCard identifier.
tx_at
stringdate-timeRequiredTransaction time (ISO 8601).
card_tx_data
objectRequiredNormalized transaction fields.
JSON
{
  "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_topup

Sent for every card topup result.

FieldTypeRequiredDescription
type
stringRequiredEvent type. Enum: card_topup
card_id
stringRequiredCard identifier.
tx_at
stringdate-timeRequiredTopup time (ISO 8601).
currency
stringRequiredTopup currency (ISO 4217).
received_amount
stringRequiredAmount received (decimal string).
order_id
stringOptionalAssociated order id, when available.
JSON
{
  "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_change

We normalize provider status as in the freeze/unfreeze endpoints. Only the end state is delivered.

FieldTypeRequiredDescription
type
stringRequiredEvent type. Enum: card_status_change
card_id
stringRequiredCard identifier.
changed_at
stringdate-timeRequiredChange time (ISO 8601).
new_status
stringRequiredNormalized card status. Enum: pending_activation, active, frozen, closed
JSON
{
  "type": "card_status_change",
  "card_id": "697c9b7559fad5ba001068ce",
  "changed_at": "2026-02-03T22:37:11.597606+00:00",
  "new_status": "frozen"
}

4. Card OTP

card_otp

OTP payloads include the code and the time it was sent.

FieldTypeRequiredDescription
type
stringRequiredEvent type. Enum: card_otp
card_id
stringRequiredCard identifier.
code
stringRequiredOne-time password.
sent_at
stringdate-timeRequiredOTP send time (ISO 8601).
JSON
{
  "type": "card_otp",
  "card_id": "697c9b7559fad5ba001068ce",
  "code": "687524",
  "sent_at": "2026-02-03T22:37:11.597606+00:00"
}

5. Master account topup

master_account_topup

Sent 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.
FieldTypeRequiredDescription
type
stringRequiredEvent type. Enum: master_account_topup
status
stringRequiredDeposit status. Enum: completed
currency
stringRequiredMaster account currency.
master_account_balance
objectRequiredMaster account balance before and after credit.
tx_info
objectRequiredBlockchain transaction details.
credited_at
stringdate-timeRequiredCredit time (ISO 8601).
JSON
{
  "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 + timestamp for deduplication.
  • Always respond quickly with {"success": true} once your handler has accepted the event.