> ## 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 Python

> Use parse_webhook_event from the agentrail package 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, verify that signature against the raw request body and your webhook secret. The `parse_webhook_event` function handles verification and deserialization in one call.

## Import

```python theme={null}
from agentrail import parse_webhook_event
```

## Verify and parse a delivery

```python theme={null}
from agentrail import parse_webhook_event

raw_body = request.body
signature = request.headers["X-AgentRail-Signature"]

event = parse_webhook_event(
    raw_body,
    os.environ["AGENTRAIL_WEBHOOK_SECRET"],
    signature,
)

print(event.type, event.data)
```

`parse_webhook_event` raises an exception if the signature does not match or if the body cannot be deserialized.

## Parameters

| Parameter       | Type           | Description                                         |
| --------------- | -------------- | --------------------------------------------------- |
| `raw_body`      | `bytes \| str` | The raw request body, unmodified                    |
| Second argument | `str`          | Your webhook secret from `AGENTRAIL_WEBHOOK_SECRET` |
| `signature`     | `str`          | The value of the `X-AgentRail-Signature` header     |

## Use the raw request body

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

In FastAPI, read the raw body with `Request.body()`:

```python theme={null}
import os

from fastapi import FastAPI, Request
from agentrail import parse_webhook_event

app = FastAPI()

@app.post("/webhooks/agentrail")
async def handle_webhook(request: Request):
    raw_body = await request.body()
    signature = request.headers["X-AgentRail-Signature"]

    event = parse_webhook_event(
        raw_body,
        os.environ["AGENTRAIL_WEBHOOK_SECRET"],
        signature,
    )

    print(event.type, event.data)
    return {"ok": True}
```

In Django or Flask, read `request.body` before any JSON parsing middleware runs.

## 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.
