> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sagepilot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Receiving replies

> Verify and process asynchronous Agent API reply callbacks.

Agent API V1 delivers replies asynchronously. After Sagepilot accepts a customer message, eligible AI-agent and human-agent replies are sent to the channel's configured HTTPS response URL as `support.message.created` events.

## Callback request

```json theme={null}
{
  "id": "evt_custom_channel_reply_01",
  "type": "support.message.created",
  "version": "v1",
  "occurred_at": "2026-08-16T10:30:03Z",
  "workspace_id": "8920e14b-176b-483e-a079-8ecb4f731aaa",
  "channel_id": "2c4bc69a-77f7-47ea-9d9a-44019e77a36e",
  "channel_type": "custom_channel",
  "payload": {
    "chat_id": "8c89f8e2-3ef7-4d1a-bcf8-2ec5fcb67e21",
    "customer": {
      "id": "b8240e37-272d-4c60-806d-ddd0ac8f93f8",
      "external_id": "cust-4821",
      "name": "Priya",
      "email": "priya@example.com",
      "phone": "+919876543210"
    },
    "message": {
      "id": "0d9f6a3e-3fd5-4d72-bd36-5e2224535844",
      "role": "pilot",
      "content": "Your order shipped yesterday and arrives tomorrow.",
      "attachments_count": 1,
      "attachment_types": ["application/pdf"],
      "type": "message",
      "in_reply_to_external_message_id": "msg-001",
      "attachments": [
        {
          "id": "sage-attachment-id",
          "file_name": "invoice.pdf",
          "mime_type": "application/pdf",
          "size": 4096,
          "url": "https://sagepilot-download.example.com/signed-download"
        }
      ]
    }
  }
}
```

Important fields:

* `id` is the stable webhook event ID. Use it to deduplicate retries.
* `channel_type` is `custom_channel`, the backend identifier for Agent API.
* `payload.chat_id` is Sagepilot's resolved chat ID. It is output-only and must not be sent in later inbound message events.
* `payload.customer.external_id` is the workspace-unique customer ID supplied by your backend.
* `payload.message.role` is `pilot` for an AI-agent reply or `agent` for a human-agent reply.
* `payload.message.in_reply_to_external_message_id`, when present, refers to your inbound `message.external_id`.
* Attachment `url` values are short-lived Sagepilot download URLs, currently valid for 30 minutes. Download or copy files promptly.

Optional or unknown values may be omitted or `null`. Do not depend on customer profile fields always being present.

## Verify the callback signature

Sagepilot sends these headers:

| Header                   | Description                                    |
| ------------------------ | ---------------------------------------------- |
| `X-Sagepilot-Event-Id`   | Same value as the body `id`.                   |
| `X-Sagepilot-Event-Type` | Same value as the body `type`.                 |
| `X-Sagepilot-Timestamp`  | Timestamp generated for this delivery attempt. |
| `X-Sagepilot-Signature`  | Lowercase hexadecimal HMAC-SHA256 signature.   |

Compute the expected signature over the timestamp, one period, and the exact raw request body:

```text theme={null}
{X-Sagepilot-Timestamp}.{exact_raw_request_body}
```

```javascript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

/** Verify a Sagepilot Agent API callback against its exact raw body. */
function verifyAgentApiCallback({ rawBody, timestamp, signature, signingKey }) {
  const expected = createHmac("sha256", signingKey)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const suppliedBytes = Buffer.from(signature, "utf8");
  const expectedBytes = Buffer.from(expected, "utf8");
  return (
    suppliedBytes.length === expectedBytes.length &&
    timingSafeEqual(suppliedBytes, expectedBytes)
  );
}
```

Verify the signature before parsing or processing the JSON body. Also confirm that the event and header IDs/types match and reject timestamps outside your replay window.

<Warning>
  Inbound request signatures use `v1={hex_digest}` over the method, path, timestamp, and raw body. Callback signatures contain the raw hexadecimal digest and use the separate `{timestamp}.{raw_body}` framing shown above.
</Warning>

## Acknowledge and deduplicate

Return a `2xx` response quickly after durably accepting the event. Process slow application work asynchronously.

Sagepilot retries callback delivery for transport errors, timeouts, HTTP `429`, and HTTP `5xx`. Other HTTP `4xx` responses are treated as non-retryable. Because delivery is at least once, store and deduplicate on the event `id` before applying side effects.

Your response body is ignored and should not contain customer data or secrets.
