Create PDF Async — Queued PDF Generation with Webhooks
Queue a PDF for rendering and receive the result later — either by polling a job endpoint or via a webhook notification. Best for large documents, batches, or any flow where you don’t want to hold open an HTTP connection until rendering completes.
POST https://api.templatefox.com/v1/pdf/create-asyncAuthentication: API key in x-api-key header.
Credits: 1 credit deducted when the job is queued (refunded if the job fails permanently).
For immediate generation, use create-pdf instead.
How it works
Section titled “How it works”POST /v1/pdf/create-asyncwith yourtemplate_idanddata— you immediately receive ajob_id.- The job moves through
pending→processing→completedorfailed. - You get the result one of two ways (or both):
- Polling — call
GET /v1/pdf/jobs/{job_id}untilstatusiscompletedorfailed. - Webhook — pass a
webhook_urlin the request; we POST to it when the job resolves.
- Polling — call
Both delivery mechanisms are supported simultaneously. A webhook is optional.
Submit a job
Section titled “Submit a job”Minimum required
Section titled “Minimum required”| Field | Type | Description |
|---|---|---|
template_id | string | The 12-character template ID. |
data | object | Key-value pairs matching your template variables. |
curl -X POST https://api.templatefox.com/v1/pdf/create-async \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "HMQywVpZxqAM",
"data": {
"customer_name": "John Doe",
"invoice_number": "INV-001"
},
"webhook_url": "https://example.com/webhooks/pdf"
}' Or use one of our official SDKs — TypeScript, Python, Go, PHP, Ruby, Java, C#.
All parameters
Section titled “All parameters”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
template_id | string | ✅ | — | 12-character template ID. |
data | object | ✅ | — | Key-value data merged into the template. |
export_type | string | — | "url" | Currently "url" is the only supported value for async. Use create-pdf for binary responses. |
expiration | integer | — | 86400 | Signed URL expiration in seconds (60–604800). |
filename | string | — | "document" | Custom filename (no extension). |
store_s3 | boolean | — | false | Upload to your configured S3 bucket instead of our CDN. |
s3_filepath | string | — | — | Path prefix inside your S3 bucket. |
s3_bucket | string | — | — | Override the bucket name from your S3 integration. |
webhook_url | string | — | — | Public HTTPS URL to notify on completion or failure. |
webhook_secret | string | — | — | Secret (16–256 chars) used to HMAC-SHA256 sign webhook payloads. |
pdf_variant | string | — | — | "pdf/a-1b", "pdf/a-2b", or "pdf/a-3b" for archival compliance. |
version | string | — | — | Template version tag or number. |
Example: signed webhook + S3 upload
Section titled “Example: signed webhook + S3 upload”A fully configured async request — webhook with HMAC signing, S3 upload, custom filename, PDF/A compliance.
curl -X POST https://api.templatefox.com/v1/pdf/create-async \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "HMQywVpZxqAM",
"data": { "invoice_number": "INV-001" },
"filename": "invoice-001",
"store_s3": true,
"s3_filepath": "invoices/2026/04/",
"pdf_variant": "pdf/a-2b",
"webhook_url": "https://example.com/webhooks/pdf",
"webhook_secret": "whk_a1b2c3d4e5f6g7h8i9j0k1l2"
}' When the job completes, the webhook payload will include result.s3_bucket and result.s3_key instead of result.url, and every request will carry an X-TemplateFox-Signature header — see Verifying the signature.
Response
Section titled “Response”{ "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "pending", "credits_remaining": 99}| Field | Type | Description |
|---|---|---|
job_id | string | UUID to poll or correlate with webhook notifications. |
status | string | Always "pending" at creation time. |
credits_remaining | number | Credits left after deduction. |
Poll job status
Section titled “Poll job status”GET /v1/pdf/jobs/{job_id}
Section titled “GET /v1/pdf/jobs/{job_id}”Get the current status of a job.
curl https://api.templatefox.com/v1/pdf/jobs/550e8400-e29b-41d4-a716-446655440000 \
-H "x-api-key: YOUR_API_KEY" Status values
Section titled “Status values”| Status | Description |
|---|---|
pending | Queued, waiting to be picked up by a worker. |
processing | A worker is rendering the PDF. |
completed | PDF is ready — check result_url or result_s3_bucket/result_s3_key. |
failed | Rendering failed — check error_message and error_code. Credit is refunded. |
Response
Section titled “Response”{ "id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "template_id": "HMQywVpZxqAM", "export_type": "url", "filename": "invoice-001", "store_s3": false, "created_at": "2026-04-24T12:00:00Z", "updated_at": "2026-04-24T12:00:05Z", "completed_at": "2026-04-24T12:00:05Z", "result_url": "https://cdn.templatefox.com/generated/abc123/invoice-001.pdf", "result_filename": "invoice-001.pdf", "result_expires_at": "2026-04-25T12:00:05Z", "result_s3_bucket": null, "result_s3_key": null, "error_message": null, "error_code": null}Polling recommendations
Section titled “Polling recommendations”- Every 1–2 seconds for small documents.
- Every 5–10 seconds for large documents or batches.
- Use exponential backoff if you expect many jobs in flight.
- When possible, prefer webhooks to avoid the polling overhead.
Webhooks
Section titled “Webhooks”If you provide webhook_url, we POST to it once the job resolves — no polling needed.
Headers
Section titled “Headers”| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | TemplateFox-Webhook/1.0 |
X-TemplateFox-Event | pdf.completed or pdf.failed |
X-TemplateFox-Timestamp | Unix epoch seconds |
X-TemplateFox-Signature | sha256=<hex> — only when webhook_secret is set |
Completion payload
Section titled “Completion payload”{ "event": "pdf.completed", "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "result": { "url": "https://cdn.templatefox.com/generated/abc123/invoice-001.pdf", "filename": "invoice-001.pdf" }}For S3 uploads, the result object contains s3_bucket and s3_key instead of url.
Failure payload
Section titled “Failure payload”{ "event": "pdf.failed", "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "failed", "error": { "message": "Template rendering failed: missing variable 'customer_name'", "code": "RENDER_ERROR" }}Verifying the signature
Section titled “Verifying the signature”When a webhook_secret is set, we sign every webhook with HMAC-SHA256:
signature = HMAC_SHA256(secret, timestamp + "." + raw_body)Compare it to the value of X-TemplateFox-Signature after the sha256= prefix. Using the raw request body (before JSON parsing) is important — any re-serialization will invalidate the signature.
import hmac
import hashlib
def verify_signature(secret: str, timestamp: str, raw_body: bytes, signature_header: str) -> bool:
if not signature_header.startswith("sha256="):
return False
received = signature_header.removeprefix("sha256=")
expected = hmac.new(
secret.encode("utf-8"),
f"{timestamp}.{raw_body.decode('utf-8')}".encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(received, expected) Retries
Section titled “Retries”We retry failed webhook deliveries up to 3 times with exponential backoff (1s, 5s, 30s):
- Retried on network errors and 5xx responses.
- Not retried on 4xx responses (your endpoint actively rejected the request).
Respond with a 2xx status code within a few seconds to acknowledge receipt.
List jobs
Section titled “List jobs”GET /v1/pdf/jobs
Section titled “GET /v1/pdf/jobs”List jobs belonging to your team. Supports pagination and status filtering.
| Query parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Page size (max 100). |
offset | integer | 0 | Skip this many results. |
status | string | — | Filter by pending, processing, completed, or failed. |
curl "https://api.templatefox.com/v1/pdf/jobs?status=failed&limit=50" \
-H "x-api-key: YOUR_API_KEY" Response
Section titled “Response”{ "jobs": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "failed", "template_id": "HMQywVpZxqAM", "export_type": "url", "created_at": "2026-04-24T12:00:00Z", "error_message": "Template rendering failed", "error_code": "RENDER_ERROR" } ], "total": 3, "limit": 50, "offset": 0}When to use async vs sync
Section titled “When to use async vs sync”Use create-pdf (sync) | Use create-pdf-async |
|---|---|
| Short documents, simple data | Large documents or heavy rendering |
| Interactive request/response flow | Batch or background jobs |
| You need binary bytes directly | You want webhook callbacks |
| Low-volume, single-PDF requests | High-volume parallel generation |