Webhooks
Receive real-time event notifications via HTTP callbacks.
Instead of polling for job completion, register a webhook URL and SymageDocs will
POST a signed JSON payload whenever a subscribed event fires.
Events
| Event | Fires when |
|---|---|
job.completed | A generation job finishes successfully (includes presigned download URLs) |
job.failed | A generation job fails |
batch.completed | A batch generate request completes (includes per-item download URLs) |
batch.exhausted | A batch's token budget is fully consumed |
Register a Webhook
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | HTTPS endpoint to receive events (max 2,048 chars) |
events | string[] | no | Event types to subscribe to (default: all three) |
description | string | no | Optional label (max 500 chars) |
curl -X POST https://symagedocs.ai/api/v1/webhooks \
-H "x-user-id: YOUR_USER_ID" \
-H "Content-Type: application/json" \
-d '{{"url": "https://your-server.com/hooks", "events": ["job.completed", "job.failed"]}}'
signing_secret is returned once at creation. Store it securely — you need it to verify payload signatures.Manage Webhooks
Inline Webhook Registration
Instead of pre-registering webhooks, pass a webhook_url parameter when creating a job or generating batch items.
A temporary webhook subscription is auto-created for the relevant events.
curl -X POST https://symagedocs.ai/api/v1/generate \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{{"form_id": "irs_w2_2025", "quantity": 10, "webhook_url": "https://your-server.com/hooks"}}'
Payload Format
Every delivery is a POST with a JSON body. job.completed and batch.completed payloads include presigned download URLs:
{{
"event": "job.completed",
"timestamp": "2026-03-30T12:05:00+00:00",
"webhook_id": "550e8400-...",
"data": {{
"job_id": "661e9500-...",
"status": "completed",
"form_id": "irs_w2_2025",
"quantity": 100,
"credits_charged": 2000,
"completed_at": "2026-03-30T12:05:00+00:00",
"download_urls": [
{{"filename": "irs_w2_2025_abc123.pdf", "url": "https://s3...presigned"}},
{{"filename": "irs_w2_2025_abc123.json", "url": "https://s3...presigned"}}
]
}}
}}
Headers
| Header | Description |
|---|---|
X-Symagedocs-Signature | HMAC-SHA256 hex digest of the raw request body |
X-Symagedocs-Event | Event type (e.g., job.completed) |
X-Symagedocs-Delivery | Unique delivery ID for deduplication |
Verifying Signatures
Verify the X-Symagedocs-Signature header to ensure the payload is authentic and untampered:
import hashlib, hmac
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
# In your handler:
body = request.body # raw bytes
sig = request.headers["X-Symagedocs-Signature"]
if not verify_webhook(body, sig, WEBHOOK_SECRET):
return Response("Invalid signature", status=401)
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {{
const expected = crypto.createHmac("sha256", secret)
.update(body).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signature)
);
}}
// In your handler:
const body = await request.text();
const sig = request.headers.get("X-Symagedocs-Signature");
if (!verifyWebhook(body, sig, WEBHOOK_SECRET)) {{
return new Response("Invalid signature", {{ status: 401 }});
}}
Retry Policy
Failed deliveries (non-2xx response or network error) are retried with exponential backoff:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 10 seconds |
| 2nd retry | 60 seconds |
| 3rd retry | 5 minutes |
After all retries are exhausted the delivery is marked as failed.
Your webhook remains active and continues receiving future events.
2xx response quickly (within 10 seconds). Do heavy processing asynchronously after acknowledging receipt.Best Practices
- Always verify signatures before processing payloads.
- Use the
X-Symagedocs-Deliveryheader to deduplicate — retries resend the same delivery ID. - Respond quickly — return
200immediately and process the event asynchronously. - Use HTTPS for your webhook URL to protect payloads in transit.
- Monitor delivery failures — if your endpoint is consistently failing, SymageDocs will continue attempting future events but individual deliveries will exhaust retries.