Conversion Webhooks
TheAccessiblePDF can POST a signed notification to your endpoint when a conversion finishes, including a pre-signed link to download all outputs. Webhooks are managed per user (or per tenant) in Settings β Integrations β Webhooks on pdf.theaccessible.org, or via the API below.
A ready-to-deploy receiver lives in
examples/webhook-receiver-worker/
(Cloudflare Worker).
Events
| Event | When |
|---|---|
conversion.completed | A conversion finished and outputs are downloadable |
conversion.failed | A conversion failed terminally |
ping | Sent by the βSend testβ button / POST /api/webhooks/:id/test |
Both the synchronous convert path and the async chunked path (used for uploads, email intake, and S3 integrations) dispatch these events.
Delivery payload
{ "event": "conversion.completed", "fileId": "68d657f0-5cc2-4ca9-b3fe-b8662872d489", "userId": "1407fe63-e1cb-4e7a-9959-262782a73c73", "tenantId": null, "timestamp": "2026-07-06T23:14:37.000Z", "data": { "originalName": "1pagetxt.pdf", "backend": "chunked-vision", "wcagStatus": "passed", "pages": 1, "zipUrl": "https://api-pdf.theaccessible.org/api/download/<fileId>/zip?token=<signed>", "zipExpiresAt": "2026-07-07T23:14:37.000Z" }}data.zipUrlβ pre-signed download of a zip containing the accessible HTML, accessible PDF, and conversion report. Requires no auth; expires 24 hours after dispatch (zipExpiresAt). Served byGET /api/download/:fileId/zip?token=(workers/api/src/routes/download.ts).data.wcagStatusβ"passed"or"failed"from the WCAG audit run at the end of the conversion.
Signature verification
Every delivery carries:
X-Webhook-Signature: sha256=<hex HMAC-SHA256>X-Webhook-Timestamp: <unix seconds>X-Webhook-ID: <endpoint uuid>Content-Type: application/jsonThe HMAC key is your endpoint secret (shown once at creation); the input
is "{timestamp}.{rawBody}". Receivers must:
- Reject if
|now β timestamp| > 300seconds (replay protection). - Compute the HMAC over the raw body bytes (not re-serialized JSON).
- Compare with a constant-time equality check.
Reference implementations: sender in
workers/api/src/utils/webhook-sign.ts, receiver in
examples/webhook-receiver-worker/src/index.ts.
Secret rotation
POST /api/webhooks/:id/rotate-secret returns a new secret immediately;
the old secret keeps verifying for a 1-hour grace period
(rotating_until), so you can roll receivers without dropped deliveries.
Delivery semantics
| Behavior | Value |
|---|---|
| Success criterion | Any 2xx response within 10 seconds |
| Retries | 3 attempts: immediate, +1 min, +5 min |
| Auto-disable | After 10 consecutive exhausted-retry failures (email sent; re-enable in settings resets the counter) |
| Rate limits | 60/h per endpoint, 100/h per user, 500/h per tenant |
| Overflow queue | Rate-limited deliveries queue (cap 100/endpoint, drained every minute); drops beyond the cap trigger an email with the affected download links |
Deliveries are at-least-once in principle β make handlers idempotent,
keyed on fileId. The full delivery history (status, HTTP code,
response body, latency) is visible per endpoint in the settings UI or
via GET /api/webhooks/:id/deliveries.
Management API
All routes require a signed-in session (Bearer JWT), base
https://api.theaccessible.org:
| Route | Purpose |
|---|---|
POST /api/webhooks | Register endpoint {url, events?, description?, tenantId?} β returns the secret once |
GET /api/webhooks | List your endpoints |
PATCH /api/webhooks/:id | Update url, events, description, enabled |
DELETE /api/webhooks/:id | Remove endpoint |
POST /api/webhooks/:id/rotate-secret | Rotate secret (1-hour dual-validity) |
GET /api/webhooks/:id/deliveries | Paginated delivery history |
POST /api/webhooks/:id/test | Fire a ping at the endpoint and report the result |
Endpoint URLs must be public HTTPS (private/link-local/metadata IPs are
rejected β see workers/api/src/utils/webhook-url-validator.ts).
Tenant-scoped endpoints (tenantId) receive events for every user in
the tenant and are manageable by tenant admins.
Implementation map
| Piece | Location |
|---|---|
| Dispatcher (queue, retries, rate limits) | workers/api/src/services/webhook-dispatcher.ts |
| Dispatch call sites | workers/api/src/scheduler/chunk-scheduler.ts, workers/api/src/routes/convert.ts |
| CRUD + test + history routes | workers/api/src/routes/webhooks.ts |
| Signing | workers/api/src/utils/webhook-sign.ts |
| Pre-signed zip tokens | workers/api/src/utils/presigned-url.ts |
| Tables | webhook_endpoints, webhook_deliveries |
| Queue drainer | 1-minute interval on Node server + CF cron (server.ts, index.ts) |