# Dynamic Document API: Webhooks

> Webhook endpoints, the delivery log and replays.

> Part of the Dynamic Document API documentation. Index of all pages for agents: [llms.txt](https://dynamicdocumentapi.com/llms.txt), every page in one file: [llms-full.txt](https://dynamicdocumentapi.com/llms-full.txt).

11 endpoints of the [API reference](https://dynamicdocumentapi.com/docs/api-reference/index.md), version 1.0.0. Interactive reference: https://dynamicdocumentapi.com/docs/api-reference#tag/webhooks. Machine-readable OpenAPI document: https://dynamicdocumentapi.com/openapi/openapi-public.json

## Servers

- https://api-eu.dynamicdocumentapi.com — EU (Germany)

## Endpoints

- `GET /v1/events/samples/{event_type}` — Get a sample event
- `GET /v1/webhook-deliveries` — List webhook deliveries
- `GET /v1/webhook-deliveries/{delivery_id}` — Retrieve a webhook delivery
- `POST /v1/webhook-deliveries/{delivery_id}/replay` — Replay a webhook delivery
- `GET /v1/webhook-endpoints` — List webhook endpoints
- `POST /v1/webhook-endpoints` — Create a webhook endpoint
- `GET /v1/webhook-endpoints/{endpoint_id}` — Retrieve a webhook endpoint
- `PATCH /v1/webhook-endpoints/{endpoint_id}` — Update a webhook endpoint
- `DELETE /v1/webhook-endpoints/{endpoint_id}` — Delete a webhook endpoint
- `POST /v1/webhook-endpoints/{endpoint_id}/roll-secret` — Roll the signing secret
- `POST /v1/webhook-endpoints/{endpoint_id}/test` — Send a test event

## GET /v1/events/samples/{event_type}

**Get a sample event**

A made-up event of this type, shaped like a real delivery's body, e.g. to set up a receiver or a no-code trigger before the first real event. It is the body a test event (`POST /v1/webhook-endpoints/{id}/test`) sends, but nothing is delivered. Its object carries `test: true` and the reference `"test_event"`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `event_type` (required) | path | "api_key.expiring" \| "batch.completed" \| "batch.progress" \| "email.bounced" \| "email.complained" \| "email.delivered" \| "email.send_failed" \| "email.sent" \| "render.expired" \| "render.failed" \| "render.succeeded" \| "storage.upload_failed" \| "template.published" \| "usage.threshold_reached" |  |

Responses:

- `200` 
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Unknown event type.
- `429` Rate limited (`Retry-After`).

## GET /v1/webhook-deliveries

**List webhook deliveries**

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `cursor` | query | string |  |
| `endpoint_id` | query | string |  |
| `limit` | query | integer |  |
| `render_id` | query | string |  |
| `status` | query | "exhausted" \| "failed" \| "pending" \| "succeeded" |  |

Responses:

- `200` 
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `429` Rate limited (`Retry-After`).

## GET /v1/webhook-deliveries/{delivery_id}

**Retrieve a webhook delivery**

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `delivery_id` (required) | path | string |  |

Responses:

- `200` 
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Not found.
- `429` Rate limited (`Retry-After`).

## POST /v1/webhook-deliveries/{delivery_id}/replay

**Replay a webhook delivery**

Sends the stored event again as a new delivery (`replay_of` set, same event id). An endpoint's delivery goes to the endpoint's current URL. A delivery to a render's or batch's own `webhook.url` goes to that URL again, signed with the secret of the endpoint that signed the original (the workspace's default endpoint).

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |
| `delivery_id` (required) | path | string |  |

Responses:

- `202` 
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Not found.
- `409` `conflict`: the endpoint is deleted, or disabled; for a delivery to a per-request `webhook.url`, the endpoint whose secret signs it. Test events can still be replayed to a disabled endpoint.
- `429` Rate limited (`Retry-After`).

## GET /v1/webhook-endpoints

**List webhook endpoints**

Endpoints registered for this workspace. Secrets are never returned by a read.

Responses:

- `200` Every object in the workspace (a single page today).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## POST /v1/webhook-endpoints

**Create a webhook endpoint**

The response carries the signing `secret` **once** — store it now. Deliveries are signed per [Standard Webhooks](https://www.standardwebhooks.com/); see [webhooks](https://dynamicdocumentapi.com/docs/webhooks).

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`, required): WebhookEndpointWrite

| Field | Type | Description |
|---|---|---|
| `description` | string |  |
| `enabled` | boolean |  |
| `events` (required) | array of "render.succeeded" \| "render.failed" \| "render.expired" \| "batch.progress" \| "batch.completed" \| "storage.upload_failed" \| "email.sent" \| "email.send_failed" \| "email.delivered" \| "email.bounced" \| "email.complained" \| "usage.threshold_reached" \| "template.published" \| "api_key.expiring" |  |
| `headers` | object | Extra request headers. |
| `is_default` | boolean | The default endpoint's secret signs per-request webhooks (a render's `webhook.url`); it gets no extra events. `true` makes this endpoint the default in place of the current one. On create, `false` keeps the new endpoint from becoming the default; without the field the workspace's first endpoint becomes it. |
| `managed_by` | "zapier" \| "make" \| "n8n" \| "bubble" \| "pipedream" \| "activepieces" \| "power_automate" | Create only. Set from the OAuth token when it belongs to an integration's app (e.g. Zapier's), which may send the same value; API keys can't set it. * `zapier` - zapier * `make` - make * `n8n` - n8n * `bubble` - bubble * `pipedream` - pipedream * `activepieces` - activepieces * `power_automate` - power_automate |
| `template_ids` | array of string | Only deliver events for these templates. |
| `url` (required) | string | Absolute https URL with a public host and no credentials, on port 443, 80, 8080 or 8443. |

Responses:

- `201` Created; `secret` is shown once.
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## GET /v1/webhook-endpoints/{endpoint_id}

**Retrieve a webhook endpoint**

Common base: API key or OAuth only, never a dashboard region token.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `endpoint_id` (required) | path | string | Webhook endpoint id (`whe_…`). |

Responses:

- `200` 
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Webhook endpoint not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## PATCH /v1/webhook-endpoints/{endpoint_id}

**Update a webhook endpoint**

Partial update; omitted fields keep their value. The secret is not changed — use `POST /v1/webhook-endpoints/{id}/roll-secret`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `endpoint_id` (required) | path | string | Webhook endpoint id (`whe_…`). |

Request body (`application/json`): PatchedWebhookEndpointWrite

| Field | Type | Description |
|---|---|---|
| `description` | string |  |
| `enabled` | boolean |  |
| `events` | array of "render.succeeded" \| "render.failed" \| "render.expired" \| "batch.progress" \| "batch.completed" \| "storage.upload_failed" \| "email.sent" \| "email.send_failed" \| "email.delivered" \| "email.bounced" \| "email.complained" \| "usage.threshold_reached" \| "template.published" \| "api_key.expiring" |  |
| `headers` | object | Extra request headers. |
| `is_default` | boolean | The default endpoint's secret signs per-request webhooks (a render's `webhook.url`); it gets no extra events. `true` makes this endpoint the default in place of the current one. On create, `false` keeps the new endpoint from becoming the default; without the field the workspace's first endpoint becomes it. |
| `managed_by` | "zapier" \| "make" \| "n8n" \| "bubble" \| "pipedream" \| "activepieces" \| "power_automate" | Create only. Set from the OAuth token when it belongs to an integration's app (e.g. Zapier's), which may send the same value; API keys can't set it. * `zapier` - zapier * `make` - make * `n8n` - n8n * `bubble` - bubble * `pipedream` - pipedream * `activepieces` - activepieces * `power_automate` - power_automate |
| `template_ids` | array of string | Only deliver events for these templates. |
| `url` | string | Absolute https URL with a public host and no credentials, on port 443, 80, 8080 or 8443. |

Responses:

- `200` 
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Webhook endpoint not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## DELETE /v1/webhook-endpoints/{endpoint_id}

**Delete a webhook endpoint**

Pending deliveries for this endpoint stop; the delivery log is kept.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `endpoint_id` (required) | path | string | Webhook endpoint id (`whe_…`). |

Responses:

- `204` Deleted.
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Webhook endpoint not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## POST /v1/webhook-endpoints/{endpoint_id}/roll-secret

**Roll the signing secret**

Returns a new `secret` once. Deliveries carry both signatures until the previous secret expires, so receivers can be updated without downtime.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |
| `endpoint_id` (required) | path | string | Webhook endpoint id (`whe_…`). |

Request body (`application/json`): RollSecret

| Field | Type | Description |
|---|---|---|
| `expire_previous_in` | integer | Seconds the previous secret keeps signing deliveries (0 = immediately). |

Responses:

- `200` The new secret, shown once.
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Webhook endpoint not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## POST /v1/webhook-endpoints/{endpoint_id}/test

**Send a test event**

Queues a signed sample event (`test: true`) to the endpoint and returns the delivery id; follow it with `GET /v1/webhook-deliveries/{id}`. Every event type can be tested; the sample `data` has the real event's shape, including `data.event` for `email.delivered`, `email.bounced` and `email.complained`. Ids are made up and files have no URL.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |
| `endpoint_id` (required) | path | string | Webhook endpoint id (`whe_…`). |

Request body (`application/json`): TestEvent

| Field | Type | Description |
|---|---|---|
| `event` | "render.succeeded" \| "render.failed" \| "render.expired" \| "batch.progress" \| "batch.completed" \| "storage.upload_failed" \| "email.sent" \| "email.send_failed" \| "email.delivered" \| "email.bounced" \| "email.complained" \| "usage.threshold_reached" \| "template.published" \| "api_key.expiring" | * `render.succeeded` - render.succeeded * `render.failed` - render.failed * `render.expired` - render.expired * `batch.progress` - batch.progress * `batch.completed` - batch.completed * `storage.upload_failed` - storage.upload_failed * `email.sent` - email.sent * `email.send_failed` - email.send_failed * `email.delivered` - email.delivered * `email.bounced` - email.bounced * `email.complained` - email.complained * `usage.threshold_reached` - usage.threshold_reached * `template.published` - template.published * `api_key.expiring` - api_key.expiring |

Responses:

- `202` Queued.
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Webhook endpoint not found, or not replicated to this region yet.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).
