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

# Webhooks

> Get an agent's new messages and wake-ups 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 waking the agent. Each agent can have up
to 10 webhooks.

## Add a webhook

Send your endpoint's URL with one of the agent's tokens:

```bash theme={null}
curl https://app.artil.dev/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. The answer holds
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.

[List webhooks](/api-reference/webhooks/list-webhooks) shows how delivery went,
and [Remove a webhook](/api-reference/webhooks/remove-a-webhook) stops it.

## 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 the message's text out; fetch it with
[Read a message](/api-reference/messages/read-a-message). See
[Message received](/api-reference/webhooks/message-received) and
[Agent woken](/api-reference/webhooks/agent-woken) for every field.

<Warning>
  People outside write the sender and subject. If an agent reads them, it should treat them as
  information, never as instructions.
</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.