# Webhooks Receive real-time notifications when orders are created and fulfilled --- ACP webhooks notify your systems when order events occur. When a checkout completes, a signed HTTP POST is sent to your configured webhook URL with the order details. ## Setting Up Webhooks 1. Log into [build.chipp.ai](https://build.chipp.ai) and navigate to your app 2. Go to **Publish > Catalog** 3. Find your API key in the API Keys section 4. Click to configure the webhook URL and secret Each API key has its own webhook configuration. You can set: - **Webhook URL** -- The HTTPS endpoint that receives events - **Webhook Secret** -- A secret string used to sign payloads for verification > **Note:** Webhook URLs must be publicly accessible HTTPS endpoints. The webhook secret is used to generate HMAC-SHA256 signatures so you can verify that requests are authentic. ## Event Types | Event | Trigger | |-------|---------| | `order.created` | An order was created from a completed checkout (manual fulfillment) | | `order.fulfilled` | An order was automatically fulfilled (tool execution succeeded) | When a checkout with automatic fulfillment products completes, the event type will be `order.fulfilled`. For manual fulfillment products, the event type will be `order.created`. ## Webhook Payload ```json { "event": "order.fulfilled", "order_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "checkout_session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "application_id": "d4e5f6a7-b8c9-0123-def0-123456789abc", "status": "fulfilled", "fulfillment_result": [ { "toolName": "analyze_data", "success": true, "output": "Analysis complete" } ], "total": { "amount": 2500, "currency": "usd" }, "created_at": "2025-01-15T12:05:00.000Z" } ``` ### Payload Fields | Field | Type | Description | |-------|------|-------------| | `event` | string | The event type (see table above) | | `order_id` | string (UUID) | Unique order identifier | | `checkout_session_id` | string (UUID) | The checkout session that created this order | | `application_id` | string (UUID) | The application this order belongs to | | `status` | string | Current order status | | `fulfillment_result` | array or null | Results from automatic fulfillment (tool execution) | | `total.amount` | integer | Order total in currency minor units | | `total.currency` | string | ISO currency code | | `created_at` | string (ISO 8601) | When the order was created | ## Webhook Headers Every webhook request includes these headers: | Header | Description | |--------|-------------| | `Content-Type` | `application/json` | | `X-ACP-Event` | The event type (e.g., `order.fulfilled`) | | `X-ACP-Timestamp` | Unix timestamp (seconds) when the webhook was sent | | `X-ACP-Signature` | HMAC-SHA256 signature for payload verification | ## Signature Verification Verify webhook authenticity by computing the HMAC-SHA256 signature and comparing it to the `X-ACP-Signature` header. The signature is computed over the string `{timestamp}.{body}`, where `{timestamp}` is the `X-ACP-Timestamp` header value and `{body}` is the raw JSON request body. ### Node.js Example ```javascript import crypto from "crypto"; function verifyWebhook(req, webhookSecret) { const signature = req.headers["x-acp-signature"]; const timestamp = req.headers["x-acp-timestamp"]; const body = req.body; // raw string, not parsed JSON const expected = crypto .createHmac("sha256", webhookSecret) .update(`${timestamp}.${body}`) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(signature, "hex"), Buffer.from(expected, "hex") ); } ``` ### Python Example ```python import hmac import hashlib def verify_webhook(headers, body, webhook_secret): signature = headers["X-ACP-Signature"] timestamp = headers["X-ACP-Timestamp"] expected = hmac.new( webhook_secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) ``` > **Note:** Always use constant-time comparison (like `timingSafeEqual` or `hmac.compare_digest`) when verifying signatures to prevent timing attacks. ## Delivery Behavior - **Timeout**: Each delivery attempt has a 10-second timeout - **Retries**: Up to 3 delivery attempts (1 initial + 2 retries) with exponential backoff (1s, 2s delays) - **Success**: A `2xx` HTTP response from your endpoint is considered successful delivery - **Failure**: After all retries fail, the error is recorded on the order. You can check the order status via the API ## Best Practices - **Respond quickly**: Return a `200` response as soon as you receive the webhook. Process the payload asynchronously if needed. - **Handle duplicates**: Webhooks may be delivered more than once. Use the `order_id` to deduplicate. - **Verify signatures**: Always verify the `X-ACP-Signature` header before processing the payload. - **Use HTTPS**: Webhook URLs must use HTTPS to protect payload contents in transit.