# 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. Useful for syncing ACP orders into your PM tool, your CRM, or your dispatch board.

## Setting up webhooks

1. Log into [app.avcodex.com](https://app.avcodex.com) and navigate to your agent.
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": "generate_audit_report", "success": true, "output": "Audit complete" }
  ],
  "total": {
    "amount": 25000,
    "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 agent 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 plus 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.

*AVCodex · Your AV expertise. Amplified by AI.*