Infini sends HTTP POST requests to the merchant's pre-configured Webhook address when order or subscription status changes, notifying the latest status and exceptions, facilitating shipping, service activation, inventory unlock, and customer service handling.
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 receive:
- order.create
- Subscribe to order.update → will receive:
- order.processing
- order.completed
- order.expired
- order.late_payment
Order Event Type Description:
- order.created: 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 (within 24 hours).
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).
If you just want to check if events are triggered and see the real webhook fields, you can use Webhook Cool testing tool, which provides you with a unique WEBHOOK URL to receive these events.
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.
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)
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: 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.
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
}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).
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.