callback_url to receive webhook notifications when the task completes or fails. This avoids polling and lets you integrate with your backend asynchronously.
Enabling Callbacks
Passcallback_url at the top level of your request (outside the input object):
string
Optional webhook URL. When provided, the API sends a POST request to this URL when the task completes or fails. Must be a publicly reachable
http:// or https:// URL — other schemes are rejected at task creation.Webhook Payload Format
The API sends a POST request to yourcallback_url with a JSON body. The payload structure depends on whether the task succeeded or failed.
Success Payload
When the task completes successfully:data object contains the output URL(s) based on the task type:
Some models may return additional fields. For example, multi-output models might include
image_urls or audio_urls as arrays.
Failure Payload
When the task fails:Request Details
- Method: POST
- Content-Type:
application/json - Body: JSON payload as shown above
Webhook Security
Your webhook endpoint is a public URL — anyone who discovers it can POST a fake “task completed” payload. Webhook signing lets your server prove two things about every delivery before acting on it:- Authenticity — the request really came from Unifically.
- Integrity — the payload was not modified in transit. The signature covers the entire request body, so changing even one byte invalidates it.
Enabling signing
Enable webhook signing from your dashboard (Settings → Webhook signing). You’ll get a 64-character hex signing secret — store it in your server’s environment (e.g.UNIFICALLY_WEBHOOK_SECRET) and never commit it to a repository. The toggle can be switched off at any time (your secret is kept, so re-enabling later needs no changes on your side); Reset secret rotates it to a new value.
Once enabled, every callback delivery carries two extra headers:
The signature is computed as:
Verifying a delivery
- Read the two
x-webhook-*headers. If signing is enabled and either is missing → reject. - Recompute the HMAC over
timestamp + "." + raw body, using the header value and the raw request bytes — not re-serialized JSON. - Compare with a constant-time comparison. On mismatch, respond
401and do not process the payload. - (Optional, recommended) Check timestamp freshness to block replay of captured deliveries. The tolerance is entirely your choice — the examples below use 5 minutes, but tighten, loosen, or remove it to match your needs. Unifically does not enforce any window; the timestamp is signed so you can trust it either way.
Common mistakes
- Verifying against re-serialized JSON.
JSON.stringify(req.body)may reorder keys or change whitespace and will not match the bytes we signed. Always keep and use the raw request body. - Comparing with
===/==. String equality leaks timing information. Usecrypto.timingSafeEqual/hmac.compare_digest. - Forgetting what skipping the timestamp check means. It’s your call to make — but without any freshness window, a captured delivery stays replayable forever. If you skip it, deduplicate on
task_idinstead. - Reading the timestamp from anywhere else. Verify with the exact
x-webhook-timestampheader value — it’s part of the signed message, so a forged timestamp breaks the signature by itself.
Rotating and disabling
- Reset secret (dashboard) rotates the secret. Deliveries signed with the old secret stop verifying immediately, so update your server’s environment right after resetting. Use this if the secret may have leaked.
- Disabling the toggle stops signing; callbacks are delivered without signature headers again. Your secret is kept, so re-enabling later restores it and your server needs no change.
Delivery Retries
If your endpoint is unreachable or returns a non-2xx status, delivery is retried up to 4 more times: at 5 minutes, 10 minutes, 30 minutes, and 1 hour after the task finished. After the 1-hour mark, automatic delivery stops. Every attempt is signed independently with a fresh timestamp and signature, so a retried delivery always passes a correct verifier — never cache or pin signatures. The full attempt history is visible on the task viaGET /v1/tasks/{task_id} as callback_result.
Manual redelivery
If your system was down and all automatic retries were exhausted, you can re-fire the webhook for any finished task from the dashboard: open Task Logs, select the task, and click Retry callback. This sends one fresh, signed delivery attempt to the stored callback URL and appends it to the task’scallback_result history.
Automatic and manual attempts share one budget: a task’s delivery log holds at most 10 attempts total (up to 5 automatic, the rest manual). Once the log is full, further manual retries are refused.
Best Practices
- Respond quickly — Return a 2xx status code within a few seconds to acknowledge receipt. Process the payload asynchronously if needed.
- Verify webhook origin — Enable webhook signing and verify the
x-webhook-signatureheader as described above. - Handle duplicates — The same webhook may be delivered more than once (retries, manual redelivery); use
task_idto deduplicate. - Validate URLs — Ensure your callback URL is publicly accessible and accepts POST requests.
