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

# Verify incoming AgentRail webhook events in TypeScript

> Use parseWebhookEvent from @agentrail-core/sdk to verify the HMAC signature on webhook deliveries before processing the event data.

AgentRail signs each webhook delivery with an HMAC signature. Before you process any payload, you must verify that signature against the raw request body and your webhook secret. The `parseWebhookEvent` function handles verification and deserialization in one call.

## Import

```typescript theme={null}
import { parseWebhookEvent } from "@agentrail-core/sdk";
```

## Verify and parse a delivery

```typescript theme={null}
import { parseWebhookEvent } from "@agentrail-core/sdk";

const event = parseWebhookEvent(rawBody, process.env.AGENTRAIL_WEBHOOK_SECRET!, {
  "x-agentrail-subscription-id": req.headers["x-agentrail-subscription-id"],
  "x-agentrail-event-id": req.headers["x-agentrail-event-id"],
  "x-agentrail-event-type": req.headers["x-agentrail-event-type"],
  "x-agentrail-delivery-id": req.headers["x-agentrail-delivery-id"],
  "x-agentrail-delivery-attempt": req.headers["x-agentrail-delivery-attempt"],
  "x-agentrail-signature": req.headers["x-agentrail-signature"],
});

console.log(event.type, event.data);
```

`parseWebhookEvent` throws if the signature does not match or if required headers are missing.

## Required headers

AgentRail sends all of the following headers with every delivery. Pass them exactly as received — do not normalize or rename them.

| Header                         | Description                                                |
| ------------------------------ | ---------------------------------------------------------- |
| `x-agentrail-subscription-id`  | ID of the webhook subscription that triggered the delivery |
| `x-agentrail-event-id`         | Unique ID for this event                                   |
| `x-agentrail-event-type`       | Event type, e.g. `task.updated`                            |
| `x-agentrail-delivery-id`      | Unique ID for this delivery attempt                        |
| `x-agentrail-delivery-attempt` | Attempt number (starts at `1`)                             |
| `x-agentrail-signature`        | HMAC signature to verify                                   |

## Use the raw request body

<Warning>
  Pass the raw request body bytes or string to `parseWebhookEvent`. Do not parse the body as JSON first. JSON re-serialization changes byte ordering and whitespace, which breaks the HMAC signature check.
</Warning>

In Express, read the raw body by configuring `express.raw`:

```typescript theme={null}
import express from "express";
import { parseWebhookEvent } from "@agentrail-core/sdk";

const app = express();

app.post(
  "/webhooks/agentrail",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const event = parseWebhookEvent(
      req.body,
      process.env.AGENTRAIL_WEBHOOK_SECRET!,
      {
        "x-agentrail-subscription-id": req.headers["x-agentrail-subscription-id"] as string,
        "x-agentrail-event-id": req.headers["x-agentrail-event-id"] as string,
        "x-agentrail-event-type": req.headers["x-agentrail-event-type"] as string,
        "x-agentrail-delivery-id": req.headers["x-agentrail-delivery-id"] as string,
        "x-agentrail-delivery-attempt": req.headers["x-agentrail-delivery-attempt"] as string,
        "x-agentrail-signature": req.headers["x-agentrail-signature"] as string,
      },
    );

    console.log(event.type, event.data);
    res.status(200).send("OK");
  },
);
```

## Set the webhook secret

Your webhook secret (`AGENTRAIL_WEBHOOK_SECRET`) is separate from your API key. You set it when you create a webhook subscription — see [POST /event-subscriptions](/docs/api/webhooks/subscriptions) for the `secret` parameter.
