POST /tasks or POST /remix, you can provide a callback_url to receive an HTTP notification when the task finishes. This lets you avoid polling GET /tasks/{task_id} and instead react to completion events asynchronously.
Setting up a webhook
Include acallback_url when starting a task:
succeeded, failed, or canceled), Moda sends a POST request to your callback_url.
Event types
Closed set. Moda only fires webhooks for the event types below. Anything else your integration sees is a bug and should be reported.| Event | When it fires |
|---|---|
task.succeeded | A design task finished successfully |
task.failed | A design task failed (transient or dead-lettered — see data.error.retryable) |
task.canceled | A design task was canceled |
export.succeeded | An async canvas export finished successfully |
export.failed | An async canvas export failed |
queued, running, expired) do not fire webhooks today. If you need running/progress beats, poll GET /tasks/{task_id} or open an issue — we’ll expand the enum on concrete customer need.
Webhook payload
The payload is a JSON event envelope wrapping the canonicalTask object under a data key. Same shape for every event type.
Envelope fields
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID (prefixed evt_…). Stable across retries of this event |
type | string | One of the event types listed above |
created | string | ISO 8601 timestamp when the event was generated |
api_version | string | Canonical Moda-Version of the data payload (currently 2026-05-01) |
data | object | Canonical Task object. See Get Task for details |
task.failed / export.failed events, inspect data.error.retryable to distinguish transient failures (worth retrying your own downstream work) from dead-lettered ones.
Verifying webhook signatures
Every webhook request includes two headers for signature verification:| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 signature in the format v1=<hex> |
X-Webhook-Timestamp | Unix timestamp (seconds) when the webhook was sent |
{timestamp}.{request_body} using HMAC-SHA256. To verify:
- Extract the timestamp from the
X-Webhook-Timestampheader - Concatenate:
{timestamp}.{raw_request_body} - Compute
HMAC-SHA256(signing_secret, concatenated_string) - Compare the result with the signature value after the
v1=prefix
Verification example
Retry behavior
If your endpoint returns a non-2xx status code or does not respond within 30 seconds, Moda retries the webhook up to 3 times with exponential backoff:| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 5 seconds |
| 3rd retry | 30 seconds |
GET /tasks/{task_id}.
Best practices
- Return 200 quickly. Process the webhook payload asynchronously to avoid timeouts. Acknowledge receipt first, then handle the event.
- Verify signatures. Always validate the
X-Webhook-Signatureheader before trusting the payload. - Check timestamps. Reject webhooks with timestamps more than 5 minutes old to prevent replay attacks.
- Handle duplicates. In rare cases, the same event may be delivered more than once. Use the envelope
id(evt_…) as an idempotency key — it’s stable across retries of the same event. - Use HTTPS. Your
callback_urlmust use HTTPS to protect the payload in transit.
Delivery log & manual replay
Every webhook attempt is recorded in a delivery log you can query and replay. Useful when your endpoint was down, a signature-verification bug slipped through, or you just want to confirm “did this fire?”- List deliveries:
GET /webhook_deliveries(reference) — cursor-paginated, filterable by event type, status, API key, and date range. Response rows include the target URL, HTTP status code, response body excerpt (first 2KB), and attempt count. - Replay a delivery:
POST /webhook_deliveries/{id}/redeliver(reference) — queues a fresh attempt with the original payload to the original URL. Returns 202 with the new delivery’s ID so you can follow it.