## 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](https://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. ```json { "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. ```json { "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. ```json { "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. ```json { "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. ```json { "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. ```json { "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. ```json { "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: ```python 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 ```python @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: | 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.