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
- Log into build.chipp.ai and open your app
- Go to Launch > Monetize and open the Catalog tab
- In the API Keys section, click Webhook on the key your agent uses
- Enter the webhook URL, click Generate for a signing secret (or paste your own, up to 64 characters), copy the secret, and click Save
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. The secret is never shown again after you save it. Leave the field blank when you edit the URL later to keep the current secret.
To stop deliveries, open the same form and click Remove webhook. You can also manage the webhook through the API with PUT /api/applications/{appId}/acp-keys/{keyId}/webhook and a body of { "webhookUrl": "https://...", "webhookSecret": "..." } (webhookUrl: null removes it).
Webhook URLs must be publicly reachable HTTPS endpoints. We refuse http:// URLs, localhost, .local and .internal hosts, and any URL whose host is or resolves to a private, loopback, link-local, or carrier-grade NAT address (for example 10.0.0.0/8, 192.168.0.0/16, or 169.254.169.254). An unsafe URL is rejected with a 400 and code INVALID_WEBHOOK_URL when you save it.
Event Types
| Event | Trigger |
|---|---|
order.created | An order was created from a completed checkout but not automatically fulfilled (order status confirmed) |
order.fulfilled | An order was automatically fulfilled (at least one bound tool execution succeeded) |
When a checkout completes and at least one automatic product’s bound tool runs successfully, the event type will be order.fulfilled. Otherwise (manual products, automatic products with no bound tool, or all tool executions failed), the event type will be order.created. Webhooks fire only for checkouts created through the external API with an API key that has a webhook URL.
Webhook Payload
{
"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 | Lowercase ISO 4217 code of the products in the order (for example usd, eur, gbp) |
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
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
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)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
2xxHTTP response from your endpoint is considered successful delivery - Redirects: Redirects are not followed. A
3xxresponse counts as a failed attempt, so point the webhook at the final URL - URL re-check: The URL is checked again before each delivery, because DNS can change after you save it. If it now points to a private address, nothing is sent and the order records
Webhook URL refused: ...as its webhook error - 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
200response 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_idto deduplicate. - Verify signatures: Always verify the
X-ACP-Signatureheader before processing the payload. - Use HTTPS: Webhook URLs must use HTTPS to protect payload contents in transit.