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.
Non-terminal states (
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
For
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:
Each API key has its own unique webhook signing secret, shown once when the key is created. You can find it in the credentials banner alongside your API key in Settings > Developer > REST API. Store it securely — it cannot be retrieved later.
The signature is computed over the string
{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
Moda makes up to 3 delivery attempts total (the initial delivery plus 2 retries). If your endpoint returns a non-2xx status code or does not respond within 30 seconds, Moda retries with a short backoff:
A persistent rejection (e.g., your endpoint returning
401 because signature verification is failing) exhausts all three attempts within a few seconds and the webhook is then dropped. You can still retrieve the task status via GET /tasks/{task_id} — the task itself completes independently of callback delivery — or replay the dropped delivery from the delivery log (see below) once your endpoint is fixed.
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.