Infini sends HTTP POST requests to the merchant's pre-configured Webhook address when an order, subscription, or card changes, or when a card authorization challenge is generated. These notifications provide the latest state needed for fulfillment, service activation, card operations, reconciliation, and customer support.
Subscription types can be configured in the merchant backend, with different subscription types corresponding to the following events:
Order events:
- Subscribe to
order.create→ will receiveorder.create - Subscribe to granular order update events as needed:
order.processingorder.completedorder.expiredorder.late_payment
- Existing endpoints that still subscribe to legacy
order.updatecontinue to receive the granular order update events above (andpayment.failedwhere applicable). New subscriptions should use the granular event names.
Order Event Type Description:
- order.create: Order created successfully, entering awaiting payment status.
- order.processing: Order entering processing (received partial payment or pending on-chain transaction confirmations).
- order.completed: Order amount satisfied, order status is paid.
- order.expired: Order not completed within validity period, expired.
- order.late_payment: Payment received after order expiration.
Automatic refund events:
- Subscribe to
auto_refund.completed→ will receiveauto_refund.completed - Subscribe to
auto_refund.unclaimed→ will receiveauto_refund.unclaimed
Automatic Refund Event Type Description:
- auto_refund.completed: An automatic refund was successfully sent on-chain.
- auto_refund.unclaimed: The payer did not submit a refund address before the confirmation deadline; the amount is settled to the merchant instead.
See Automatic Refund for when these events are produced.
Subscription events:
- Subscribe to subscription.update → will receive:
- subscription.update
- Subscribe to subscription.cancel → will receive:
- subscription.cancel
Subscription Event Type Description:
- subscription.update: Subscription status updated (e.g. activated after first payment, billing period renewed after successful renewal payment).
- subscription.cancel: Subscription canceled (by merchant API, user unsubscribe, or system due to non-payment).
Card events:
- Subscribe to
card.status_change→ will receive:card.status_change
- Subscribe to
card.transaction→ will receive:card.transaction
- Subscribe to
card.challenge→ will receive:card.challenge
Card Event Type Description:
card.status_change: Current card lifecycle snapshot, including successful activation, suspension, reactivation, and final deletion.card.transaction: Card transaction update, including card application, top-up, redeem, consume, refund, reversal, clearing, and the latest transaction status.card.challenge: One-time authorization code challenge for a card transaction. This event must be subscribed explicitly and cannot be manually resent from the developer console after terminal delivery.
Note:
- New webhook configurations should subscribe using exact event names. Wildcard subscriptions are not supported by the current webhook management API.
- Card webhooks are not emitted for personal card activity.
card.challengeis delivered only beforeexpires_at; handle it immediately and return HTTP 200 only after recording it safely.
To inspect incoming webhook requests, you can use Webhook Cool, which provides a unique HTTPS URL for receiving events.
To generate real card transaction events in the Sandbox environment, use the Card Simulator. Enter a card UUID and select one of the supported scenarios:
- Consumption authorization
- Authorization failure
- Full or partial settlement
- Authorization reversal
- Refund of a settled transaction
Before running a simulation, configure a Sandbox webhook endpoint and explicitly subscribe it to card.transaction. The simulator triggers the real Sandbox transaction-processing flow; it does not display the merchant callback and does not simulate card.status_change or card.challenge.
Webhook request body is in JSON format, including fields:
- event: Event type (e.g. order.create)
- order_id: Unique order identifier
- client_reference: Merchant-side order number (i.e. client_reference)
- amount: Order payable amount (fiat amount)
- currency: Order currency (e.g. USD)
- status: Order status:
- pending
- processing
- paid
- partial_paid
- expired
- amount_confirming: Confirming amount (on-chain transaction exists but has not reached confirmation requirement)
- amount_confirmed: Confirmed amount (on-chain confirmation completed)
- created_at: Order creation time (Unix timestamp, seconds)
- updated_at: Order last update time (Unix timestamp, seconds)
- exception_tags (if any): Order exception tag array (e.g. ["underpaid", "late"]), see "Core Business Concepts" chapter for details
Note:
- amount_confirmed + amount_confirming reflects the total payment amount identified on-chain.
- Order status combined with expiration status, confirmed/confirming amounts constitute the current order semantics.
Automatic refund webhook bodies include the order context plus an auto_refund object:
- event:
auto_refund.completedorauto_refund.unclaimed - event_id: Stable event identifier for idempotency
- order_id / client_reference / amount / currency / status
- amount_confirmed / amount_confirming
- refunded_amount: Present on
auto_refund.completed(refund principal in original token) - payments: Source payments included in this refund group
- auto_refund:
- id: Automatic refund ID
- original_payment_ids: Source payment IDs
- reason:
underpayment/overpayment/late_payment - status:
completedorunclaimed - currency / network
- refund_amount: Planned refund principal in original token
- gas_fee: Gas deducted from the refund principal
- received_amount: Amount the payer receives after gas
- refund_address / tx_hash: Present when the refund was completed on-chain; null on unclaimed
- claim_period_days / claim_deadline: Present on
auto_refund.unclaimed - created_at / completed_at: Present on
auto_refund.completed
Subscription webhook request body is in JSON format, including fields:
- event: Event type (
subscription.updateorsubscription.cancel) - subscription_id: System-generated subscription ID
- merchant_sub_id: Merchant-defined subscription ID
- plan_name: Subscription plan name
- trigger_method: Billing trigger method (e.g.
invoice) - status: Subscription status:
- pending
- active
- canceled
- currency: Subscription currency (e.g. USD)
- amount: Recurring amount per billing period
- interval_unit: Billing interval unit (
DAYorMONTH) - interval_count: Number of intervals per billing cycle
- payer_email: Payer email address
- current_period_start: Current billing period start time (Unix timestamp, seconds)
- current_period_end: Current billing period end time (Unix timestamp, seconds)
- subscription_end_at: Subscription termination time (Unix timestamp, seconds)
- next_invoice_at: Next invoice send time (Unix timestamp, seconds)
- cancel_reason: Cancel reason (included only when subscription is canceled)
- canceled_at: Cancel time (Unix timestamp, seconds; included only when subscription is canceled)
- created_at: Subscription creation time (Unix timestamp, seconds)
- updated_at: Subscription last update time (Unix timestamp, seconds)
Card webhooks use a versioned public envelope:
id: Webhook delivery ID, also carried inX-Webhook-Event-Idevent: Event type such ascard.status_changeversion: Current public envelope version, currently1occurred_at: Event occurrence time (Unix timestamp, seconds)data: Event-specific payload
Order and subscription webhooks keep the existing legacy JSON payload shape without the version / data wrapper.
The data object includes:
card.card_idcard.alias(if configured)card.last_four(if available)card.statuscard.currency
Note:
card.statuscan beinit,pending,active,suspend, ordeleted.- Successful card issuance is represented by
card.status_changewithcard.status: active; there is no separatecard.activatedevent. pending_deleteis an internal deletion transition and is not emitted as a standalonecard.status_changewebhook. During delete flows, wait fordeletedor queryGET /v2/cards/status.
Example:
{
"id": "b7ef2c62-6177-4ea8-84ec-3080f2db58f0",
"event": "card.status_change",
"version": 1,
"occurred_at": 1763513200,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
}
}
}The data object includes:
cardtransaction_idrelated_transaction_id(for refunds / reversals when available)type:apply_card,top_up,redeem,consume,refund,reversal, orclearingstatus:pending,authorized,completed, orfailedamountfeecurrencydirection:debit,credit, ornonetransaction_amountandtransaction_currency(when the provider supplies the original transaction amount and currency)merchant(if provider merchant information is available)transaction_atsettled_atfailure(only for failed transactions)auth_settle_adjustment(for a completedconsumeevent when authorization context is available)
All monetary values are decimal strings. Optional fields are omitted when the provider does not supply them.
Transaction types:
type | Meaning |
|---|---|
apply_card | Card application or issuance ledger transaction |
top_up | Funds loaded to the card |
redeem | Funds redeemed from the card |
consume | Merchant purchase authorization or settlement |
refund | Funds returned for a settled purchase |
reversal | Authorization canceled and reserved funds released |
clearing | Provider clearing transaction or adjustment |
Transaction statuses:
status | Meaning |
|---|---|
pending | Transaction has been accepted but is not yet authorized or completed |
authorized | Consumption authorization succeeded and funds are reserved |
completed | Transaction reached its final successful state |
failed | Transaction failed; direction is none and failure is included |
One transaction can produce multiple card.transaction webhooks. For example, a consumption normally moves from authorized to completed. Each delivery has a different top-level id and X-Webhook-Event-Id; correlate the updates using data.transaction_id. For refunds and reversals, related_transaction_id identifies the original consumption transaction when it can be resolved.
For a completed consumption, auth_settle_adjustment describes any difference between the amount reserved during authorization and the final settlement:
authorized_total = authorized_amount + authorized_fee
settled_total = settled_amount + settled_fee
signed_delta = authorized_total - settled_total
balance_delta = balance_after - balance_before = signed_deltadirection: release: the authorization total was greater than the settlement total, so funds were released.direction: additional_debit: the settlement total was greater than the authorization total, so additional funds were debited.direction: none: the authorization and settlement totals were equal.
Consumption authorized:
{
"id": "f72f6cd7-1f38-5e96-b60f-f54e56c8db63",
"event": "card.transaction",
"version": 1,
"occurred_at": 1786417200,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
},
"transaction_id": "8c1db7df-3d4e-44de-b6b3-5ed451809d79",
"type": "consume",
"status": "authorized",
"amount": "10",
"fee": "0",
"transaction_amount": "10",
"currency": "USD",
"transaction_currency": "USD",
"direction": "debit",
"merchant": {
"name": "Amazon"
},
"transaction_at": 1786417200
}
}Consumption completed with a lower settlement amount:
{
"id": "54b1090a-307e-5ec6-9adb-e877415222fd",
"event": "card.transaction",
"version": 1,
"occurred_at": 1786417260,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
},
"transaction_id": "8c1db7df-3d4e-44de-b6b3-5ed451809d79",
"type": "consume",
"status": "completed",
"amount": "8",
"fee": "0",
"transaction_amount": "8",
"currency": "USD",
"transaction_currency": "USD",
"direction": "debit",
"merchant": {
"name": "Amazon"
},
"transaction_at": 1786417200,
"settled_at": 1786417260,
"auth_settle_adjustment": {
"authorized_amount": "10",
"authorized_fee": "0",
"settled_amount": "8",
"settled_fee": "0",
"signed_delta": "2",
"direction": "release",
"amount": "2",
"balance_delta": "2",
"balance_before": "90",
"balance_after": "92"
}
}
}Authorization failed:
{
"id": "1ae7c21c-71b4-5fd8-9f12-2a44f94bab41",
"event": "card.transaction",
"version": 1,
"occurred_at": 1786417320,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
},
"transaction_id": "f14c387d-02d1-41b8-aebb-1c9fda914385",
"type": "consume",
"status": "failed",
"amount": "1000",
"fee": "0",
"transaction_amount": "1000",
"currency": "USD",
"transaction_currency": "USD",
"direction": "none",
"merchant": {
"name": "Amazon"
},
"transaction_at": 1786417320,
"failure": {
"reason": "Insufficient balance"
}
}
}Authorization reversed:
{
"id": "b3ab57ae-18f9-5ab7-8338-1202ea946994",
"event": "card.transaction",
"version": 1,
"occurred_at": 1786417380,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
},
"transaction_id": "7b9bb337-27f6-4d2a-b474-f91cc26a5e4c",
"related_transaction_id": "8c1db7df-3d4e-44de-b6b3-5ed451809d79",
"type": "reversal",
"status": "completed",
"amount": "10",
"fee": "0",
"currency": "USD",
"direction": "credit",
"transaction_at": 1786417380,
"settled_at": 1786417380
}
}Settled transaction refunded:
{
"id": "ebc55ba6-5607-516e-902c-9400ec44c5bf",
"event": "card.transaction",
"version": 1,
"occurred_at": 1786417440,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
},
"transaction_id": "27dfc395-f703-4b16-90ca-f1cb9d3f1ec8",
"related_transaction_id": "8c1db7df-3d4e-44de-b6b3-5ed451809d79",
"type": "refund",
"status": "completed",
"amount": "8",
"fee": "0",
"currency": "USD",
"direction": "credit",
"transaction_at": 1786417440,
"settled_at": 1786417440
}
}The data object includes:
cardchallenge_idchallenge_type(currentlyauthorization_code)challengeexpires_at
Provider-only fields such as provider webhook IDs, provider transaction IDs, merchant routing IDs, encrypted ciphertext, PAN, and CVV are not exposed in the public payload.
Example:
{
"id": "12e7c9c8-598b-4dc5-b0c2-d7b6b8c3d8a4",
"event": "card.challenge",
"version": 1,
"occurred_at": 1763513400,
"data": {
"card": {
"card_id": "a441831c-a5c7-4bed-8f61-793738afd5bc",
"alias": "Travel card",
"last_four": "1234",
"status": "active",
"currency": "USD"
},
"challenge_id": "challenge-1",
"challenge_type": "authorization_code",
"challenge": "123456",
"expires_at": 1763513700
}
}When sending Webhooks, Infini includes the following HTTP Headers for security verification and idempotent processing:
- Content-Type: application/json
- X-Webhook-Timestamp: Unix timestamp (seconds)
- X-Webhook-Event-Id: Unique event identifier, used for idempotent deduplication
- X-Webhook-Signature-Version: Signature version. The current value is
v1. - X-Webhook-Signature: HMAC-SHA256 signature, used for merchant-side signature verification
It is recommended that merchants use X-Webhook-Event-Id for idempotent processing to avoid duplicate consumption of the same event.
Card webhooks are always signed and require a usable Webhook Secret. Verify the signature against the exact raw request body before parsing JSON.
The following examples show Webhook content in typical scenarios.
After order creation, status is pending, waiting for user payment.
{
"event": "order.create",
"order_id": "10290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "pending",
"amount_confirmed": "0",
"amount_confirming": "0",
"created_at": 1763512195,
"updated_at": 1763512195
}Payment received, but transaction still confirming on blockchain.
{
"event": "order.processing",
"order_id": "20290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "processing",
"amount_confirmed": "0",
"amount_confirming": "0.5",
"created_at": 1763512349,
"updated_at": 1763512403
}Partial payment has been confirmed on blockchain.
{
"event": "order.processing",
"order_id": "20290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "processing",
"amount_confirmed": "0.5",
"amount_confirming": "0",
"created_at": 1763512349,
"updated_at": 1763512453
}Full payment received and confirmed, order completed.
{
"event": "order.completed",
"order_id": "20290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "paid",
"amount_confirmed": "1",
"amount_confirming": "0",
"created_at": 1763512349,
"updated_at": 1763512573,
"payments": [
{
"id": "pay_9c1e2f3a4b5c",
"method": "crypto_transfer",
"status": "paid",
"currency": "USD",
"amount": "1",
"network": "ETH",
"tx_hash": "0xabc123def456...",
"paid_amount": "1.000000"
}
]
}Order timeout without receiving any payment.
{
"event": "order.expired",
"order_id": "10290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "expired",
"amount_confirmed": "0",
"amount_confirming": "0",
"created_at": 1763512195,
"updated_at": 1763512255
}Order timeout but received partial payment, not reaching order amount.
{
"event": "order.expired",
"order_id": "60290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "partial_paid",
"amount_confirmed": "0.5",
"amount_confirming": "0",
"created_at": 1763514565,
"updated_at": 1763514765
}Payment received within 24 hours after order expiration.
{
"event": "order.late_payment",
"order_id": "30290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "",
"amount": "1",
"currency": "USD",
"status": "expired",
"amount_confirmed": "1",
"amount_confirming": "0",
"created_at": 1763512622,
"updated_at": 1763512815
}Tips:
- In late_payment scenarios, order status remains expired, but amount_confirmed has reached the order amount. Merchants can decide whether to ship or refund based on business strategy.
- It is recommended to make business decisions combined with exception tags (such as late, underpaid, overpaid).
- If automatic refund is enabled, eligible late / under / over payments may also produce
auto_refund.*events later. See Automatic Refund.
{
"event": "auto_refund.completed",
"event_id": "evt_xxx",
"order_id": "20290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "ORDER-10001",
"amount": "100",
"currency": "USD",
"status": "partial_paid",
"amount_confirmed": "50",
"amount_confirming": "0",
"refunded_amount": "50",
"created_at": 1763512349,
"updated_at": 1763513573,
"payments": [
{
"id": "pay_8b9c0d1e2f3a",
"method": "Crypto Transfer",
"status": "paid",
"currency": "USD",
"amount": "30",
"paid_currency": "USDT",
"paid_amount": "30",
"network": "TRON",
"tx_hash": "4e82a9b1...",
"created_at": 1763512400,
"completed_at": 1763512500
}
],
"auto_refund": {
"id": "ref_7f8e9d0c1b2a",
"original_payment_ids": ["pay_8b9c0d1e2f3a"],
"reason": "underpayment",
"status": "completed",
"currency": "USDT",
"refund_amount": "50",
"gas_fee": "1",
"received_amount": "49",
"network": "TRON",
"refund_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"tx_hash": "f91d38ab...",
"created_at": 1763513400,
"completed_at": 1763513573
}
}{
"event": "auto_refund.unclaimed",
"event_id": "evt_yyy",
"order_id": "20290d05-8f5c-4ecb-84f0-f78d6f30557f",
"client_reference": "ORDER-10001",
"amount": "100",
"currency": "USD",
"status": "partial_paid",
"amount_confirmed": "50",
"amount_confirming": "0",
"created_at": 1763512349,
"updated_at": 1766100000,
"payments": [
{
"id": "pay_8b9c0d1e2f3a",
"method": "Crypto Transfer",
"status": "paid",
"currency": "USD",
"amount": "50",
"paid_currency": "USDT",
"paid_amount": "50",
"network": "TRON",
"tx_hash": "4e82a9b1...",
"created_at": 1763512400,
"completed_at": 1763512500
}
],
"auto_refund": {
"id": "ref_7f8e9d0c1b2a",
"original_payment_ids": ["pay_8b9c0d1e2f3a"],
"reason": "underpayment",
"status": "unclaimed",
"currency": "USDT",
"refund_amount": "50",
"gas_fee": "1",
"received_amount": "49",
"network": "TRON",
"refund_address": null,
"tx_hash": null,
"claim_period_days": 30,
"claim_deadline": 1766100000
}
}After first payment is completed, the subscription transitions from pending to active.
{
"event": "subscription.update",
"subscription_id": "sub-9f3c1f2e",
"merchant_sub_id": "msub_001",
"plan_name": "Monthly Plan",
"trigger_method": "invoice",
"status": "active",
"currency": "USD",
"amount": "9.99",
"interval_unit": "MONTH",
"interval_count": 1,
"payer_email": "user@example.com",
"current_period_start": 1740000000,
"current_period_end": 1742678400,
"next_invoice_at": 1742592000,
"created_at": 1740000000,
"updated_at": 1740000100
}Renewal payment completed, billing period advanced to the next cycle.
{
"event": "subscription.update",
"subscription_id": "sub-9f3c1f2e",
"merchant_sub_id": "msub_001",
"plan_name": "Monthly Plan",
"trigger_method": "invoice",
"status": "active",
"currency": "USD",
"amount": "9.99",
"interval_unit": "MONTH",
"interval_count": 1,
"payer_email": "user@example.com",
"current_period_start": 1742678400,
"current_period_end": 1745356800,
"next_invoice_at": 1745270400,
"created_at": 1740000000,
"updated_at": 1742678500
}Subscription canceled by merchant API, user unsubscribe, or system due to non-payment.
{
"event": "subscription.cancel",
"subscription_id": "sub-9f3c1f2e",
"merchant_sub_id": "msub_001",
"plan_name": "Monthly Plan",
"trigger_method": "invoice",
"status": "canceled",
"currency": "USD",
"amount": "9.99",
"interval_unit": "MONTH",
"interval_count": 1,
"payer_email": "user@example.com",
"current_period_start": 1742678400,
"current_period_end": 1745356800,
"cancel_reason": "by_merchant_api",
"canceled_at": 1743000000,
"created_at": 1740000000,
"updated_at": 1743000000
}Infini will initiate POST requests to your configured Webhook URL. It is recommended that the receiving end follows these principles:
- Verify that all required Headers exist:
- X-Webhook-Signature
- X-Webhook-Timestamp
- X-Webhook-Event-Id
- Verify signature legitimacy (see next section).
- Implement idempotent processing based on X-Webhook-Event-Id (process only once).
- Business logic should be processed asynchronously, quickly return HTTP 200 to avoid timeout.
Verification steps:
- Read from Headers:
- X-Webhook-Signature (signature)
- X-Webhook-Timestamp (timestamp)
- X-Webhook-Event-Id (event ID)
- Get raw request body string payload.
- Assemble signature content string:
{timestamp}.{event_id}.{payload}- Use your WEBHOOK_SECRET for HMAC-SHA256 calculation:
signed_content = f"{timestamp}.{event_id}.{payload}"
expected_sig = hmac.new(
WEBHOOK_SECRET.encode(),
signed_content.encode(),
hashlib.sha256
).hexdigest()- Compare expected_sig with X-Webhook-Signature for consistency.
@app.route('/webhook', methods=['POST'])
def webhook_verification():
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
event_id = request.headers.get('X-Webhook-Event-Id')
if not all([signature, timestamp, event_id]):
return jsonify({"error": "Missing required headers"}), 400
payload = request.get_data(as_text=True)
signed_content = f"{timestamp}.{event_id}.{payload}"
expected_sig = hmac.new(
WEBHOOK_SECRET.encode(),
signed_content.encode(),
hashlib.sha256
).hexdigest()
if expected_sig != signature:
return jsonify({"error": "Invalid signature"}), 401
# Process valid webhook
return jsonify({"status": "ok"})If the merchant does not return HTTP 200, Infini will retry the event.
- Maximum retries: 8 times
- First 3 retry intervals: 30 seconds
- 4th-8th retries use incremental backoff strategy, example:
| Attempt | Description | Interval (Example) |
|---|---|---|
| 1st | First send | Immediate |
| 2nd | 1st failed | 30 seconds |
| 3rd | 2nd failed | 30 seconds |
| 4th | 3rd failed | 30 seconds |
| 5th | 4th failed | 60 seconds |
| 6th | 5th failed | 120 seconds |
| 7th | 6th failed | 240 seconds |
| 8th | 7th failed | 480 seconds |
If multiple retries still fail, the event will be marked as delivery failed. It is recommended that merchants investigate through logs and reconciliation tools.