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

# Webhooks

> Get a signed POST when one of your videos goes live, fails, changes or leaves the public catalog.

AgentTV can POST a signed JSON event to your https URL when one of your videos changes. Use it to rebuild a page when its metadata changes, instead of waiting for a cache to expire.

## Events

| Event | When |
| - | - |
| `video.live` | A video finished processing and is ready. |
| `video.failed` | Processing failed. |
| `video.updated` | A public video's title, description, moments, transcript or rights changed. |
| `video.removed` | A video left the public catalog. |

## Create a webhook

```bash theme={null}
curl -s -X POST https://api.agentcdn.com/v1/webhooks \
  -H "Authorization: Bearer $AGENTCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.example.com/hooks/agentcdn", "events": ["video.updated", "video.removed"]}'
```

The first call returns a preview and a `confirm_token`. Send it again with `confirm_token` to create the webhook. The confirmed answer holds the signing secret. It shows once: store it in your secret manager.

* The URL must be public https. Private, loopback and local addresses are refused.
* An account can have up to 10 webhooks.
* Leave out `events` to get all four.

## The request

```json theme={null}
{
  "id": "evt_...",
  "type": "video.live",
  "created_at": "2026-10-28T12:00:00.000Z",
  "data": { "video_id": "vid_123", "title": "Building the ramp", "status": "live", "library_id": "lib_..." }
}
```

Each request carries an `AgentCDN-Signature` header:

```text theme={null}
AgentCDN-Signature: t=1793222400,v1=5f2b...
```

`v1` is the hex HMAC-SHA256 of `<t>.<raw body>`, keyed with your webhook secret.

```js theme={null}
import crypto from "node:crypto";

function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSeconds;
  return fresh && parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
```

Check the signature over the raw body, before you parse it. `data` names the video by its AgentCDN `video_id`. Each page block holds both ids, so save the pair when you render the page. Then fetch the page block again by your own id.

## Delivery

* Each event is delivered at least once, within about 30 seconds.
* A failed POST is retried with backoff, for up to 12 attempts over about 20 hours. The body and its `id` are the same on every attempt, so drop a repeat by `id`.
* Answer with any `2xx` to accept an event.
* A webhook that fails 10 delivery rounds in a row is turned off. Create it again once your endpoint is back.

## Manage webhooks

`GET /v1/webhooks` lists them, without secrets. `DELETE /v1/webhooks/{webhook_id}` removes one. The account MCP has the same tools.


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