Webhook endpoints
Create, change, disable and delete webhook endpoints, rotate their signing secret, send test events and read or retry the delivery log.
On this page
Webhooks tell your server when PDFs and batches are ready, so you never have to poll. These nine endpoints manage where the events go; the webhooks guide explains the events themselves, the CastPDF-Signature header and how to verify it, and the retry schedule. Webhooks are included in the Starter, Growth, Pro and Scale plans.
A live key manages live endpoints (which receive live events), a test key test endpoints (test events); neither sees the other's. All responses carry cache-control: no-store. Listing, disabling and deleting also work after a downgrade to a plan without webhooks, so you can always clean up.
GET /v1/webhooks
Lists the endpoints of the key's mode, newest first.
curl https://api.castpdf.com/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY"200 with { "data": [endpoint, ...] }, each an endpoint object. The list is not paginated: a workspace has at most 10 endpoints.
POST /v1/webhooks
Adds an endpoint and returns its signing secret, only this once. Store it right away; you need it to verify the signature.
curl https://api.castpdf.com/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/castpdf-webhook", "events": ["pdf.created", "pdf.failed"]}'const res = await fetch('https://api.castpdf.com/v1/webhooks', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ url: 'https://example.com/castpdf-webhook', events: ['pdf.created', 'pdf.failed'] }),
});
const endpoint = await res.json();
console.log(endpoint.secret); // store it now: it is never shown againimport requests
res = requests.post(
"https://api.castpdf.com/v1/webhooks",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"url": "https://example.com/castpdf-webhook", "events": ["pdf.created", "pdf.failed"]},
timeout=30,
)
endpoint = res.json()
print(endpoint["secret"]) # store it now: it is never shown again{
"id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"url": "https://example.com/castpdf-webhook",
"description": null,
"events": ["pdf.created", "pdf.failed"],
"mode": "live",
"status": "enabled",
"disabled_reason": null,
"secret_prefix": "whsec_AbCd",
"created_at": "2026-10-03T10:15:02.123Z",
"updated_at": "2026-10-03T10:15:02.123Z",
"secret": "whsec_AbCdEfGhIjKlMnOpQrStUvWxYz012345"
}| Field | Description |
|---|---|
urlstringRequired | An https URL on the public internet, on port 443, 80 or 8443, up to 2,048 characters, without a user name or password. |
eventsarrayRequired | One or more of pdf.created, pdf.failed, batch.completed, each once. |
descriptionstring or null | Up to 200 characters, for you. |
mode"live" or "test" | Must match the key's mode, which is the default. |
| Code | When |
|---|---|
feature_not_available (402) | Your plan does not include webhooks |
invalid_request (400) | Validation failed, the workspace already has 10 endpoints, or the URL is not accepted: then details.field is "url" and details.reason is https_required, private_address, port, dns, credentials, scheme, invalid or too_long |
The endpoint object
| Field | Description |
|---|---|
idstring (UUID) | The endpoint's id. |
urlstring | Where events are sent. |
descriptionstring or null | Your note. |
eventsarray | The event types it receives. |
modestring | live or test. |
statusstring | enabled or disabled. |
disabled_reasonstring or null | manual (you disabled it) or failing (disabled after 24 hours of failed deliveries); null while enabled. |
secret_prefixstring | The first 10 characters of the signing secret, to tell secrets apart. |
created_at, updated_atstring | When it was created and last changed. |
GET /v1/webhooks/:id
Returns one endpoint object, without the secret.
curl https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b \
-H "Authorization: Bearer YOUR_API_KEY"Errors: webhook_not_found (404), also for an endpoint of the other mode.
PUT /v1/webhooks/:id
Changes any of url, events, description and enabled; send at least one.
curl -X PUT https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'| Field | Description |
|---|---|
url, events, description | As for POST /v1/webhooks. A new url is checked the same way. |
enabledboolean | false stops deliveries (disabled_reason: "manual"); true starts them again, also after an automatic disable (disabled_reason: "failing"). |
200 with the endpoint. Enabling an endpoint or changing its url or events needs a plan with webhooks (feature_not_available otherwise); disabling it never does. Errors: invalid_request (400), webhook_not_found (404).
DELETE /v1/webhooks/:id
Deletes the endpoint and its delivery log.
curl -X DELETE https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b \
-H "Authorization: Bearer YOUR_API_KEY"204 with no body. Errors: webhook_not_found (404).
POST /v1/webhooks/:id/rotate-secret
Creates a new signing secret and returns the endpoint with secret, shown only this once. For 24 hours, deliveries are signed with both secrets (two v1 values in the signature header), so you can switch your server to the new secret without missing events.
curl -X POST https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/rotate-secret \
-H "Authorization: Bearer YOUR_API_KEY"200 with the endpoint and its new secret. Errors: feature_not_available (402), webhook_not_found (404).
POST /v1/webhooks/:id/test
Sends a sample pdf.created event marked "test_event": true, once (no retries). Use it to check that your server answers and verifies the signature.
curl -X POST https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/test \
-H "Authorization: Bearer YOUR_API_KEY"{ "delivery_id": "4d3c2b1a-0f9e-4d8c-9b6a-5f4e3d2c1b0a" }The result appears in the delivery log a moment later. Test events and retries together are limited to 10 a minute per endpoint (rate_limited with details.reason: "webhook_sends" and a retry-after header). Errors: feature_not_available (402), webhook_not_found (404), rate_limited (429).
GET /v1/webhooks/:id/deliveries
The delivery log of the last 30 days, newest first.
curl "https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/deliveries?limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"{
"data": [
{
"id": "4d3c2b1a-0f9e-4d8c-9b6a-5f4e3d2c1b0a",
"event_id": "evt_6f1d2c3b4a5e4f609b7a1c2d3e4f5a6b",
"event_type": "pdf.created",
"status": "succeeded",
"attempts": 1,
"response_status": 200,
"response_body": "ok",
"last_error": null,
"duration_ms": 182,
"test_event": false,
"created_at": "2026-10-03T10:15:03.000Z",
"last_attempt_at": "2026-10-03T10:15:03.120Z",
"next_attempt_at": null,
"event": {
"id": "evt_6f1d2c3b4a5e4f609b7a1c2d3e4f5a6b",
"type": "pdf.created",
"created_at": "2026-10-03T10:15:03.000Z",
"livemode": true,
"data": {
"id": "3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3",
"status": "succeeded",
"pages": 2,
"bytes": 48213,
"mode": "print",
"test": false,
"template_id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
"template_version": 4,
"error_code": null,
"created_at": "2026-10-03T10:15:02.123Z",
"expires_at": "2026-10-10T10:15:02.123Z",
"url": "https://api.castpdf.com/v1/files/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3?exp=1791627302&sig=Jx0k...",
"filename": "invoice-042.pdf",
"metadata": { "order_id": "1042" },
"batch_id": null
}
}
}
],
"next_before": null
}| Query parameter | Description |
|---|---|
limit | 1 to 100. Default 20. |
before | The previous page's next_before. |
| Field | Description |
|---|---|
statusstring | pending (waiting for its next attempt at next_attempt_at), succeeded or failed (no more attempts). |
attemptsinteger | Attempts made so far. |
response_status, response_bodyinteger, string, or null | Your server's last answer; response_body keeps its first 1,024 characters. |
last_errorstring or null | Why the last attempt failed, or null. |
duration_msinteger or null | How long the last attempt took. |
test_eventboolean | true for a test event. |
eventobject | The event exactly as it was sent: id, type, created_at, livemode and data (plus test_event on test events). |
Errors: webhook_not_found (404).
POST /v1/webhooks/:id/deliveries/:deliveryId/retry
Sends a finished delivery (succeeded or failed) once more, now: one attempt, not a new retry schedule. The endpoint must be enabled.
curl -X POST https://api.castpdf.com/v1/webhooks/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/deliveries/4d3c2b1a-0f9e-4d8c-9b6a-5f4e3d2c1b0a/retry \
-H "Authorization: Bearer YOUR_API_KEY"202 with { "delivery_id": "..." }, the same delivery, which shows the new attempt in the log. Retries share the limit of 10 a minute per endpoint with test events.
| Code | When |
|---|---|
invalid_request (400) | The endpoint is disabled |
feature_not_available (402) | Your plan does not include webhooks |
webhook_not_found (404) | No such endpoint for this key's mode |
not_found (404) | No such delivery, or it is still pending |
rate_limited (429) | Over the limit for test events and retries |