This reference documents every webhook and callback that Templified sends to your server: the render-complete callback fired per render job, the Studio export notification for org-scoped destinations, and the batch-complete callback for Design Group renders.
Two distinct webhook types
Templified sends two different kinds of outbound webhook, and they serve different purposes:
-
Render-complete callback — attached to a single render job at submission time via the
webhook_urlparameter onPOST /api/v1/createorPOST /api/v1/design-groups/create. Fires once when that specific job finishes. -
Studio export notification — org-scoped. Every time a design is exported from Studio (or sent via
send_to: "integration:webhook"), Templified posts the samestudio.exportnotification body to the single URL configured in Settings → Integrations → Webhook.
Both types require an HTTPS endpoint that answers with a 2xx status within 10 seconds, and both make up to three attempts. They differ in what counts as worth retrying.
The Studio export notification retries only on a transient failure — a connection failure or timeout, or a 408, 425, 429, 500, 502, 503 or 504 response. Any other 4xx means your endpoint rejected the request and would reject it again, so delivery stops on the first attempt. The render-complete callback retries on any non-2xx response, but only a transient failure can be re-sent by hand afterwards.
Only the Studio export notification carries delivery-identifier headers. Deduplicate render-complete callbacks on job_id instead, which is stable across every attempt. See the setup guide Setting up the Studio export webhook for how to configure the org-scoped URL and HMAC signing.
Render-complete callback body
When a render job you submitted with webhook_url finishes, Templified sends POST <your_url> with Content-Type: application/json. The body contains the following fields:
| Field | Type | Description |
|---|---|---|
job_id |
string | The render job identifier, matching the job_id from the 202 submission response. |
status |
string |
"complete" or "failed". |
template_id |
string | ID of the template that was rendered. |
output_url |
string or null | Public URL of the finished image. Present when status is "complete"; null on failure. |
render_ms |
number or null | Wall-clock render time in milliseconds. Absent on failure. |
completed_at |
ISO 8601 string or null | Timestamp when the job finished. |
instance_id |
string | Present only when the render was tied to a Studio design. Omitted for pure API renders with no design counterpart. |
player_id |
string | Present only when player_id was supplied at submission time. |
Example
{
"job_id": "clxw3r2e40000abc123",
"status": "complete",
"template_id": "tmpl_abc",
"output_url": "https://cdn.templified.io/renders/org_x/clxw3.jpg",
"render_ms": 3820,
"completed_at": "2026-06-12T14:05:32.000Z",
"instance_id": "inst_xyz",
"player_id": "24"
}
Retries and failure handling
Templified attempts delivery up to three times, retrying on any response that is not a 2xx and on a connection failure or timeout. The delays between attempts are approximately 5 seconds, then 30 seconds. Every attempt is recorded with its status code and the first part of your server's response. If all three attempts fail, the job's webhook status is marked "failed" — the render itself still completed (or failed) normally and the result remains available via GET /api/v1/create/:job_id.
Once the automatic attempts are exhausted, a failed callback can be re-sent by hand from History → Renders only when the last attempt was a transient failure: a connection failure or timeout, or a 408, 425, 429, 500, 502, 503 or 504. If your endpoint answered with any other 4xx, there is nothing to re-send — fix the endpoint, then submit the render again.
Your server must respond with any 2xx HTTP status within 10 seconds. Respond quickly — do any long-running work asynchronously after acknowledging receipt.
Studio export notification (studio.export)
The org-scoped webhook fires whenever a design is sent from Studio or when a render submitted via the API includes "send_to": "integration:webhook". The body is always a studio.export event.
| Field | Type | Description |
|---|---|---|
event |
string | Always "studio.export". |
exportedAt |
ISO 8601 string | Timestamp when the export was triggered. |
orgId |
string | Your organization identifier. |
instanceId |
string or null | The design ID in Studio, or null when the render was submitted directly via POST /api/v1/create with send_to: "integration:webhook" and had no Studio design. Receivers that key on instanceId must guard for null. |
templateId |
string | ID of the template the design is based on. |
designName |
string | The name of the design as it appears in Studio. |
folder |
string | The Studio folder that contains the design. |
imageUrl |
string | Public URL of the finished render image. |
qty |
number (optional) | Number of copies requested. Omitted when the quantity is 1 (the default). Receivers can use this to loop a downstream action — for example, send to a print destination N times — without Templified firing the webhook more than once per export. |
mainEventCode |
string or null (optional) | Main event code set on the design, if any. |
destinationEventCode |
string or null (optional) | Destination event code set on the design, if any. |
playerId |
string or null (optional) | Player identifier carried from the design or render, if present. |
textLayers |
array | One object per text layer: { "name": "…", "content": "…" }. The layer's name and its resolved text value at the time of export. |
Example
{
"event": "studio.export",
"exportedAt": "2026-06-12T14:10:00.000Z",
"orgId": "org_abc",
"instanceId": "inst_xyz",
"templateId": "tmpl_abc",
"designName": "Smith, Jordan — 5x7",
"folder": "Fall 2026",
"imageUrl": "https://cdn.templified.io/renders/org_abc/inst_xyz.jpg",
"qty": 2,
"mainEventCode": "1373",
"destinationEventCode": "042",
"playerId": "12",
"textLayers": [
{ "name": "Player Name", "content": "Jordan Smith" },
{ "name": "Team", "content": "Westside Warriors" }
]
}
Delivery headers
Every request carries two headers you can rely on, in addition to Content-Type: application/json:
-
X-Templified-Delivery-Id— identifies the export. Every attempt at the same export carries the same value, including a retry started by hand from History, so a retry is never a new export. -
X-Templified-Attempt— the attempt number within the current send, as a string starting at1.
Use the delivery id as your idempotency key, not the attempt number: a re-send from History counts its attempts from 1 again, while the delivery id stays the same. This matters most when your receiver drives a side effect such as printing — a receiver that can process a request and still answer 503 will be sent that same request again, and only the delivery id tells you it is the one you already handled.
Retries and failure handling
Templified makes up to three attempts, with short delays of roughly 1 second and then 4 seconds between them. A retry fires only on a transient failure — a connection failure or timeout, or a 408, 425, 429, 500, 502, 503 or 504. Any other 4xx response stops delivery immediately, because the same request would be rejected again.
HMAC signature
If you configured an HMAC secret in Settings → Integrations → Webhook, every delivery includes an X-Templified-Signature header in the format sha256=<hex>. Compute HMAC-SHA256 of the raw request body using your secret and compare it to the header value to verify the request came from Templified.
Design Groups: one batch-complete callback per batch
When you submit a Design Group batch render via POST /api/v1/design-groups/create, you can supply two separate webhook URLs:
-
webhook_url— called once per individual render job as each template in the group finishes. The body shape matches the render-complete callback described above. -
batch_webhook_url— called exactly once when every render in the batch has reached a terminal state (all complete or failed). The body mirrors theGET /api/v1/design-groups/create/:batch_idpoll response, giving you the full batch summary in a single signal.
The batch-complete callback fires regardless of individual job outcomes — it signals that the batch is done, not that every job succeeded. Check each job's status in the batch summary to determine which renders completed and which did not.
Both webhook_url and batch_webhook_url are optional and independent. You can supply one, both, or neither.
Polling as an alternative to webhooks
If you cannot expose a public HTTPS endpoint, poll for results instead:
- Single render:
GET /api/v1/create/:job_id - Design Group batch:
GET /api/v1/design-groups/create/:batch_id
Both endpoints return the current status and, once complete, the same output fields you would receive via webhook. Polling and webhook callbacks are independent — you can use either or both on the same job.
You can also see delivery attempts without calling the API: every attempt is listed in History — under the Deliveries tab for the organization webhook, and in the Webhook column of the Renders tab for per-job callbacks. Each row shows the status code your endpoint returned, and a failure that could still land offers a Retry button, so a recoverable delivery can be re-sent without re-submitting the render.
Comments
0 comments
Article is closed for comments.