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

# Webhooks

> Get an agent's new messages posted to your server.

A webhook posts an agent's events to your server as they happen: an email or
text arriving, or someone on your team sending to the agent's
[Message URL](/url). Each agent can have up to 10 webhooks.

## Add a webhook

Open the agent in [Artil](https://app.artil.ai), click **Connect runtime** on its
**Runtime webhooks** tab, choose **Your server**, and enter your endpoint's URL.
Or send the URL with one of the agent's tokens:

```bash theme={null}
curl https://app.artil.ai/api/agents/AGENT_ID/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/artil"}'
```

The URL must be `https` and reachable on the public internet. Either way you
get the webhook's `secret`, which starts with `whsec_`. Keep it on your server:
listing webhooks leaves it out, and adding the same URL again gives back the
same webhook and secret.

The **Runtime webhooks** tab shows how delivery went and removes a webhook.
Over the API, [List webhooks](/api-reference/webhooks/list-webhooks) and
[Remove a webhook](/api-reference/webhooks/remove-a-webhook) do the same.

## What arrives

Each event is one `POST` with a JSON body:

```json theme={null}
{
  "eventId": "1234",
  "name": "message.received",
  "timestamp": "2026-10-08T12:00:00.000Z",
  "data": {
    "agent": { "id": "8f1c…", "name": "Support" },
    "message": {
      "id": "0b7e…",
      "channel": "email",
      "from": "Ada <ada@example.com>",
      "subject": "Invoice"
    }
  },
  "cursor": "1234"
}
```

The body leaves an email's or text's content out; fetch it with
[Read a message](/api-reference/messages/read-a-message). A message at the
[Message URL](/url) has channel `url` and comes whole: Artil keeps no copy, so
`data.message` carries its `text` and, in `sender`, the teammate who sent it.
See [Message received](/api-reference/webhooks/message-received) for every
field.

<Warning>
  People outside write the sender and subject; treat them as [information, never as
  instructions](/safety).
</Warning>

## Check the signature

Artil signs each delivery as [Standard Webhooks](https://www.standardwebhooks.com)
describes, in the `webhook-id`, `webhook-timestamp`, and `webhook-signature`
headers. Check it with a Standard Webhooks library before you trust the body.
In TypeScript:

```ts theme={null}
import { Webhook } from "standardwebhooks";

const webhook = new Webhook(process.env.ARTIL_WEBHOOK_SECRET);

export async function POST(request: Request) {
  const body = await request.text();
  const event = webhook.verify(body, Object.fromEntries(request.headers));

  // Act on event.name and event.data here.

  return new Response(null, { status: 204 });
}
```

Verify the raw body as it arrived, before parsing it. The library also refuses
deliveries signed more than five minutes ago.

## Retries

Answer with any `2xx` status within 5 seconds. Otherwise Artil sends the same
event again, with the same `webhook-id`, after about 10 seconds, 1 minute,
5 minutes, 30 minutes, 2 hours, and then every 6 hours. It gives up on an event
3 days after it happened.

Events arrive in order: while one is being retried, the ones after it wait. An
event can arrive more than once, so skip a `webhook-id` you have already
handled.

| Your answer | What Artil does |
| - | - |
| `2xx` | Sends the next event |
| `410 Gone` | Removes the webhook |
| `413` | Skips the event and sends the next |
| Anything else | Retries the event later |

[List webhooks](/api-reference/webhooks/list-webhooks) shows the last `error`,
how many `attempts` failed, and when Artil retries next in `retryAt`.


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