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

# Webhooks

> Get notified on your server when something happens in your ReyGrid account.

Webhooks send an HTTP `POST` request to a URL on your server whenever an event happens in your account, such as a chat request completing, an agent changing, or your credits running low. Use them to sync data, trigger workflows or alert your team without polling the API.

## How it works

1. You register an **endpoint URL** in the console and choose which **events** it receives.
2. When one of those events happens, ReyGrid sends a JSON payload to your URL.
3. Every request is **signed** with your endpoint's secret, so you can verify it really comes from ReyGrid.
4. Your server replies with a `2xx` status within 10 seconds. Anything else is treated as a failure and retried.

## Set up an endpoint

<Steps>
  <Step title="Create a route on your server">
    Add a `POST` route that accepts a JSON body and returns `200` quickly. Read the **raw** request body: you need it to [verify the signature](/rest-api/webhooks/verifying).
  </Step>

  <Step title="Add the endpoint in the console">
    Open **Console → Webhooks**, select **Add endpoint**, enter your URL and pick the [events](/rest-api/webhooks/events) you want.
  </Step>

  <Step title="Save the signing secret">
    After creating the endpoint you see its signing secret (`whsec_…`) once. Store it in your server's environment, for example `REYGRID_WEBHOOK_SECRET`. You can reveal or rotate it later from the endpoint card.
  </Step>

  <Step title="Send a test event">
    Select **Send test** on the endpoint card. ReyGrid sends a `ping` event and shows the response status in **Recent deliveries**.
  </Step>
</Steps>

## A minimal receiver

<CodeGroup>
  ```javascript Node.js (Express) theme={"system"}
  import express from "express";
  import { verifyReyGrid } from "./verify-reygrid.js"; // see "Verifying signatures"

  const app = express();

  // Keep the raw body: the signature is computed over the exact bytes we send.
  app.post("/webhooks/reygrid", express.raw({ type: "application/json" }), (req, res) => {
    const signature = req.get("X-ReyGrid-Signature");
    if (!verifyReyGrid(signature, req.body.toString("utf8"), process.env.REYGRID_WEBHOOK_SECRET)) {
      return res.status(400).send("Invalid signature");
    }

    const event = JSON.parse(req.body);

    switch (event.type) {
      case "chat.completed":
        // e.g. record usage for event.data.agent.id
        break;
      case "credits.low":
        // e.g. alert your team or top up
        break;
    }

    res.sendStatus(200); // acknowledge quickly; do heavy work in the background
  });

  app.listen(3000);
  ```

  ```python Python (Flask) theme={"system"}
  import json, os
  from flask import Flask, request, abort
  from verify_reygrid import verify_reygrid  # see "Verifying signatures"

  app = Flask(__name__)

  @app.post("/webhooks/reygrid")
  def reygrid_webhook():
      body = request.get_data()  # raw bytes, before any parsing
      signature = request.headers.get("X-ReyGrid-Signature", "")
      if not verify_reygrid(signature, body, os.environ["REYGRID_WEBHOOK_SECRET"]):
          abort(400)

      event = json.loads(body)
      if event["type"] == "chat.completed":
          pass  # e.g. record usage for event["data"]["agent"]["id"]
      elif event["type"] == "credits.low":
          pass  # e.g. alert your team

      return "", 200
  ```
</CodeGroup>

## Request format

Every delivery is a `POST` with a JSON body and these headers:

| Header | Description |
| - | - |
| `Content-Type` | `application/json` |
| `User-Agent` | `ReyGrid-Webhooks/1.0` |
| `X-ReyGrid-Event` | The event type, e.g. `chat.completed` |
| `X-ReyGrid-Delivery` | The event ID (`evt_…`). The same ID is reused when a delivery is retried |
| `X-ReyGrid-Signature` | `t=<timestamp>,v1=<signature>`, see [Verifying signatures](/rest-api/webhooks/verifying) |

The body is an event envelope:

```json theme={"system"}
{
  "id": "evt_6f1c2a9e4b7d03a58c21e0f4",
  "type": "chat.completed",
  "created": "2026-10-05T09:24:13.512Z",
  "data": { }
}
```

| Field | Type | Description |
| - | - | - |
| `id` | string | Unique event ID. Use it to ignore duplicates |
| `type` | string | The [event type](/rest-api/webhooks/events) |
| `created` | string | When the event happened (ISO 8601, UTC) |
| `data` | object | Event details. The shape depends on `type` |

## Delivery and retries

* A delivery **succeeds** when your endpoint answers with a `2xx` status within **10 seconds**.
* Redirects are **not followed**: a `3xx` response counts as a failure, so point the endpoint at the final URL.
* Failed deliveries are retried **after 30 seconds** and again **after 5 minutes**, up to 3 attempts in total.
* Every attempt, with its status code and duration, appears under **Recent deliveries** in the console for 30 days.

<Warning>
  Deliveries are at-least-once and may arrive out of order. Make your handler **idempotent**: store processed event IDs and skip any `id` you've already handled.
</Warning>

## Endpoint requirements

| Requirement | Detail |
| - | - |
| Protocol | `https://` |
| Address | Must resolve to a public IP address. Private and local networks are rejected |
| Response | `2xx` within 10 seconds |
| Limit | Up to 10 endpoints per account |

You can turn an endpoint off without deleting it with the switch on its card. Disabled endpoints receive nothing until you turn them back on.

## Next steps

<CardGroup cols={2}>
  <Card title="Events" icon="list" href="/rest-api/webhooks/events">
    Every event type and its payload.
  </Card>

  <Card title="Verifying signatures" icon="shield-check" href="/rest-api/webhooks/verifying">
    Check that requests really come from ReyGrid.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.