Webhooks
Receive real-time notifications for card events
Overview
Webhooks let you receive real-time HTTP notifications when events occur in your Infracard account. Instead of polling the API, Infracard sends POST requests to your configured endpoint when events happen.
Webhook endpoints are configured during merchant onboarding — there are no self-serve management endpoints. Contact your account manager to update your webhook URL or event subscriptions.
Event Types
| Event | Description |
|---|---|
card.activated | A card was successfully activated |
card.freeze | Card freeze operation completed or failed (check status) |
card.unfreeze | Card unfreeze operation completed or failed (check status) |
card.deposit | Card deposit processing, completed, or failed (check status) |
card.withdraw | Card withdrawal completed or failed (check status) |
card.auth_transaction | An authorization transaction was processed on a card |
card.3ds | A 3D Secure challenge was triggered |
card_holder.status_changed | A card holder's KYC/status changed |
merchant.balance_credited | Merchant balance was credited |
Payload Format
The request body contains the raw event data with no wrapper. Event metadata is sent via HTTP headers:
| Header | Description |
|---|---|
X-Webhook-Id | Unique delivery ID |
X-Event-Type | Event type (e.g. card.activated) |
X-Timestamp | Unix milliseconds when the event was dispatched |
X-Webhook-Signature | RSA-SHA256 signature of the request body |
Example payload for a card.activated event:
{
"providerOrderId": "WO2025022100001",
"orderNo": "ORD-20250221-0001",
"merchantOrderNo": "MY-REF-001",
"cardId": "card_abc123",
"loadAmount": "100.00",
"issuanceFee": "2.50",
"depositFee": "0.00",
"providerId": "wasabi",
"providerDepositFee": "0.00"
}Verifying Signatures
Infracard signs every webhook payload using RSA-SHA256 with the platform's private key. You receive the corresponding public key during onboarding.
To verify a delivery, check the X-Webhook-Signature header against the raw request body using the public key:
import crypto from 'crypto'
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----\n${process.env.INFRACARD_PUBLIC_KEY}\n-----END PUBLIC KEY-----`
function verifyWebhookSignature(
rawBody: string,
signature: string,
): boolean {
const verifier = crypto.createVerify('RSA-SHA256')
verifier.update(rawBody)
return verifier.verify(PUBLIC_KEY, signature, 'base64')
}Important: Verify against the raw request body string, not a re-serialized object. Re-serializing may change key ordering or whitespace, which will invalidate the signature.
Retry Policy
If your endpoint returns a non-2xx status code or the request times out (30 s), Infracard retries the delivery with the following backoff schedule:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 2 minutes |
| 4th retry | 10 minutes |
| 5th retry | 30 minutes |
| 6th retry | 1 hour |
| 7th retry | 2 hours |
| 8th retry | 4 hours |
After 9 total attempts (1 initial + 8 retries), the delivery is marked as failed and no further retries are made.
Best Practices
- Respond quickly. Return a
200status code as soon as you receive the webhook, then process the event asynchronously. Infracard will time out after 30 seconds. - Handle duplicates. Use the
X-Webhook-Idheader to deduplicate deliveries. The same event may be delivered more than once during retries. - Verify signatures. Always validate the
X-Webhook-Signatureheader before processing the payload to ensure the request originated from Infracard.
Event Reference
Card Lifecycle
card.activated
Fires after the card provider confirms card creation. The card is live and usable. Funds have been settled from the merchant balance.
| Field | Type | Description |
|---|---|---|
providerOrderId | string | Provider's order identifier |
orderNo | string | Infracard order number |
merchantOrderNo | string | Your order reference passed at issuance |
cardId | string | The new card's ID |
loadAmount | string | Initial card balance loaded |
issuanceFee | string | Fee charged for card issuance |
depositFee | string | Fee charged for the initial deposit |
providerId | string | Card provider identifier |
providerDepositFee | string | Fee charged by the provider for the deposit |
{
"providerOrderId": "WO2025022100001",
"orderNo": "ORD-20250221-0001",
"merchantOrderNo": "MY-REF-001",
"cardId": "card_abc123",
"loadAmount": "100.00",
"issuanceFee": "2.50",
"depositFee": "0.00",
"providerId": "wasabi",
"providerDepositFee": "0.00"
}card.freeze
Async result of a card freeze operation. On success, the card is now frozen. On fail, the card has reverted to its prior state.
| Field | Type | Description |
|---|---|---|
status | string | success or fail |
cardId | string | The card's ID |
Success:
{
"status": "success",
"cardId": "card_abc123"
}Failure:
{
"status": "fail",
"cardId": "card_abc123"
}card.unfreeze
Async result of a card unfreeze operation. On success, the card is active again. On fail, the card remains frozen.
| Field | Type | Description |
|---|---|---|
status | string | success or fail |
cardId | string | The card's ID |
Success:
{
"status": "success",
"cardId": "card_abc123"
}Failure:
{
"status": "fail",
"cardId": "card_abc123"
}Card Funding
card.deposit
Result of a card top-up. On processing, the order has been accepted and funds are frozen from the merchant balance. On success, the card balance has been loaded and merchant funds are settled permanently. On fail, frozen merchant funds are returned and the card balance is unchanged.
| Field | Type | Status | Description |
|---|---|---|---|
status | string | both | processing, success, or fail |
orderNo | string | both | Infracard order number |
merchantOrderNo | string | both | Your order reference passed at deposit |
amount | string | success | Amount deposited to the card |
depositFee | string | success | Fee charged for the deposit |
providerDepositFee | string | success | Fee charged by the provider for the deposit |
issuanceFee | string | null | success | Fee charged for card issuance (null when the deposit is not part of a card issuance) |
remark | string | null | fail | Rejection reason, if provided |
Success:
{
"status": "success",
"orderNo": "ORD-20250221-0005",
"merchantOrderNo": "idem_dep456",
"amount": "50.00",
"depositFee": "1.25",
"providerDepositFee": "0.50",
"issuanceFee": null
}Success (with issuance):
{
"status": "success",
"orderNo": "ORD-20250221-0005",
"merchantOrderNo": "idem_dep456",
"amount": "50.00",
"depositFee": "1.25",
"providerDepositFee": "0.50",
"issuanceFee": "2.50"
}Processing:
{
"status": "processing",
"orderNo": "ORD-20250221-0004",
"merchantOrderNo": "idem_dep456"
}Failure:
{
"status": "fail",
"orderNo": "ORD-20250221-0006",
"merchantOrderNo": "idem_dep789",
"remark": "Insufficient provider limits"
}card.withdraw
Result of a card withdrawal. On success, funds were reclaimed from the card back to the merchant balance. On fail, the card balance is unchanged and no funds are returned.
| Field | Type | Status | Description |
|---|---|---|---|
status | string | both | success or fail |
providerOrderId | string | both | Provider's order identifier |
idempotencyKey | string | both | The idempotency key from the original request |
providerCardId | string | success | Provider's card identifier |
amount | string | success | Amount withdrawn from the card |
remark | string | null | fail | Rejection reason, if provided |
Success:
{
"status": "success",
"providerOrderId": "WO2025022100007",
"idempotencyKey": "idem_wd001",
"providerCardId": "PC-987654",
"amount": "25.00"
}Failure:
{
"status": "fail",
"providerOrderId": "WO2025022100008",
"idempotencyKey": "idem_wd002",
"remark": null
}Transactions
card.auth_transaction
Fires every time the card is used — purchases, refunds, reversals, verifications, and fees. Use this for real-time spending visibility and reconciliation. Transaction status cycles through: PENDING → COMPLETED / DECLINED / SETTLED.
| Field | Type | Description |
|---|---|---|
providerCardId | string | Provider's card identifier |
tradeNo | string | Provider's transaction identifier |
type | string | PURCHASE, REFUND, REVERSAL, VERIFICATION, or FEE |
status | string | PENDING, COMPLETED, DECLINED, or SETTLED |
amount | string | Transaction amount |
merchantName | string | Name of the merchant where the card was used |
fee | string | Transaction fee |
crossBorderFee | string | Cross-border fee, if applicable |
settleAmount | string | null | Final settlement amount (present once settled) |
accounting | object[] | Debit/credit accounting entries (see below) |
strategyVersion | number | Fee strategy version used for this transaction |
Each entry in the accounting array:
| Field | Type | Description |
|---|---|---|
type | string | debit or credit |
amount | string | Entry amount |
referenceId | string | Operation type (e.g. card-purchase) |
externalId | string | Provider's transaction identifier |
{
"providerCardId": "PC-987654",
"tradeNo": "TXN-2025022100001",
"type": "PURCHASE",
"status": "PENDING",
"amount": "42.99",
"merchantName": "Coffee Shop",
"fee": "0.43",
"crossBorderFee": "0.00",
"settleAmount": null,
"accounting": [
{
"type": "debit",
"amount": "42.99",
"referenceId": "card-purchase",
"externalId": "TXN-2025022100001"
},
{
"type": "debit",
"amount": "0.43",
"referenceId": "card-purchase-fee",
"externalId": "TXN-2025022100001"
}
],
"strategyVersion": 1
}card.3ds
A 3D Secure challenge was triggered during an online purchase. You must deliver the OTP code or redirect URL to the cardholder within approximately 60 seconds. This event does not change any internal state — it is a pass-through from the card provider.
| Field | Type | Description |
|---|---|---|
providerCardId | string | Provider's card identifier |
tradeNo | string | Provider's transaction identifier |
type | string | third_3ds_otp (OTP code) or auth_url (redirect URL) |
decryptedValue | string | The OTP code or redirect URL |
{
"providerCardId": "PC-987654",
"tradeNo": "TXN-2025022100002",
"type": "third_3ds_otp",
"decryptedValue": "482901"
}Card Holder
card_holder.status_changed
The card holder's KYC review status changed. A status of approved means the holder can now issue cards. rejected means KYC failed. under_review means manual review is in progress.
| Field | Type | Description |
|---|---|---|
providerHolderId | string | Provider's card holder identifier |
status | string | New KYC status (e.g. approved, rejected, under_review) |
{
"providerHolderId": "PH-123456",
"status": "approved"
}Merchant
merchant.balance_credited
The merchant balance was topped up via an admin credit or an approved crypto deposit. Use this event for reconciliation.
| Field | Type | Description |
|---|---|---|
amount | string | Amount credited |
newBalance | number | Updated merchant balance after the credit |
{
"amount": "1000.00",
"newBalance": 5250.75
}