Skip to content
Last updated

7. Webhook (Order Status Callback)

Infini sends HTTP POST requests to the merchant's pre-configured Webhook address when order status changes, notifying the latest order status and exceptions, facilitating shipping, service activation, inventory unlock, and customer service handling.

7.1 Subscribable Events and Event Types

Subscription types can be configured in the merchant backend, with different subscription types corresponding to the following events:

  • Subscribe to order.create → will receive:
    • order.created
  • Subscribe to order.update → will receive:
    • order.processing
    • order.completed
    • order.expired
    • order.late_payment

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

7.1.1 Testing Tool

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.

7.2 Webhook Payload Field Description

Webhook request body is in JSON format, including fields:

  • event: Event type (e.g. order.created)
  • 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.

7.3 Webhook Request Headers

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.

7.4 Webhook Example Payloads

The following examples show Webhook content in typical scenarios.

7.4.1 Scenario 1: Order Created (order.created)

After order creation, status is pending, waiting for user payment.

{
  "event": "order.created",
  "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
}

7.4.2 Scenario 2: Order Processing (Payment Received, Confirming, order.processing)

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
}

7.4.3 Scenario 3: Order Processing (Partial Payment Confirmed, order.processing)

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
}

7.4.4 Scenario 4: Order Completed (Full Payment Received, order.completed)

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
}

7.4.5 Scenario 5: Order Expired (No Payment, order.expired)

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
}

7.4.6 Scenario 6: Order Expired (Partial Payment Received, order.expired + partial_paid)

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
}

7.4.7 Scenario 7: Payment After Order Expiration (Late Payment, order.late_payment)

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

7.5 Receiving Webhook and Security Verification

Infini will initiate POST requests to your configured Webhook URL. It is recommended that the receiving end follows these principles:

  1. Verify that all required Headers exist:
  • X-Webhook-Signature
  • X-Webhook-Timestamp
  • X-Webhook-Event-Id
  1. Verify signature legitimacy (see next section).
  2. Implement idempotent processing based on X-Webhook-Event-Id (process only once).
  3. Business logic should be processed asynchronously, quickly return HTTP 200 to avoid timeout.

7.6 Webhook Signature Verification

Verification steps:

  1. Read from Headers:
  • X-Webhook-Signature (signature)
  • X-Webhook-Timestamp (timestamp)
  • X-Webhook-Event-Id (event ID)
  1. Get raw request body string payload.
  2. Assemble signature content string:
{timestamp}.{event_id}.{payload}
  1. 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()
  1. Compare expected_sig with X-Webhook-Signature for consistency.

7.6.1 Python Verification Example

@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"})

7.7 Webhook Retry Strategy

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:
AttemptDescriptionInterval (Example)
1stFirst sendImmediate
2nd1st failed30 seconds
3rd2nd failed30 seconds
4th3rd failed30 seconds
5th4th failed60 seconds
6th5th failed120 seconds
7th6th failed240 seconds
8th7th failed480 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.