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

# Webhooks

Get the signing secret for a webhook URL, send a test delivery, read the delivery history, and rotate secrets without dropping events.

Jobs (crawls, batches, videos, routine runs) post their events to the URL you pass in `webhook`. Each delivery is signed with a secret that belongs to that URL. [Jobs, streams and webhooks](/concepts/jobs#webhooks) shows the payload and how to verify it; this page covers the secret and the history.

## Get the secret

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/webhooks/secret \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://example.com/hooks/b3os"}'
    ```
  **TypeScript**

```ts
    const { secret } = await b3os.webhooks.getSecret({ url: "https://example.com/hooks/b3os" });
    ```
  **Python**

```python
    secret = b3os.webhooks.get_secret({"url": "https://example.com/hooks/b3os"})["secret"]
    ```
  **MCP**

Webhook secrets belong in your receiver's configuration, not in an agent's context. Fetch them from code.

The first call for a URL creates its secret (`whsec_...`); later calls return the same one until you rotate it. URLs are compared after lowercasing the scheme and host and dropping a default port, so `HTTPS://Example.com:443/hooks/b3os` and `https://example.com/hooks/b3os` share a secret. The call needs `platform:write`.

## Test the receiver

```bash
curl -s https://beta-api.b3os.org/v1/webhooks/test \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/b3os"}'
```

B3OS sends one signed `webhook.test` event now and returns the delivery with the outcome of that single attempt: `status`, `last_status_code` and `last_error`. Test deliveries are not retried.

## Delivery history

`GET /v1/webhooks/deliveries` lists the last 7 days of deliveries, newest first, with their attempts, last status code, last error and next attempt. Filter with `url`, `job_id` or `status` (`pending`, `retrying`, `succeeded`, `failed`). Each delivery's `id` is the event ID in its body.

Failed deliveries are retried with backoff for 24 hours. Answer with a 2xx status quickly and do the work afterwards; deliveries come from the node through the egress guard, so receivers on private or loopback addresses never get them.

## Rotate the secret

```bash
curl -s https://beta-api.b3os.org/v1/webhooks/secret/rotate \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/b3os", "grace_s": 86400}'
```

For `grace_s` seconds (a day by default, up to 7 days) every delivery carries two `v1` signatures, one per secret, and `previous_valid_until` says when the old one stops. Deploy the new secret to your receiver within that window; a receiver that accepts any matching `v1` keeps working throughout.

## Other webhooks

- Firecrawl-compatible jobs post Firecrawl's payloads with `X-Firecrawl-Signature: sha256=<hex>`, signed with this same per-URL secret. See [Firecrawl compatibility](/compat/firecrawl).
- Webhooks into B3OS, which start routine runs, are a different thing: see [triggers](/routines/triggers#webhooks).