Skip to main content

Webhook Delivery

When a prediction reaches a terminal state, each::labs fires off an HTTP POST to your configured webhook_url. Webhooks fire only on completion or failure — there are no webhooks for intermediate states, and cancellations do not trigger one.
  • Method: POST
  • Content-Type: application/json
  • Expected response: any 2xx status to acknowledge receipt; 4xx/5xx responses trigger a retry

Payload Structure

Webhook payloads use a compact two-value status vocabulary: succeeded or failed. This differs from the richer lifecycle you see when polling — use Get Prediction if you need the full six-state view (created, starting, processing, success, error, cancelled).

Prediction Succeeded

  • exec_id — the prediction ID you received from Create Prediction
  • flow_id — the workflow ID when the prediction was triggered by a workflow, otherwise null
  • output — the prediction output; its shape depends on the model (string, array, or object)

Prediction Failed

On failure, output is an array wrapping the error detail: read output[0].error for the failure reason. The top-level error field is always an empty string.

Webhook Handler Examples

Security

Verifying Deliveries

If you set a webhook_secret when creating your prediction, each::labs signs every delivery for that prediction. Signed deliveries carry two extra headers:
  • X-Webhook-Signature — sha256= followed by a lowercase hex HMAC-SHA256 over <X-Webhook-Timestamp>.<raw request body>
  • X-Webhook-Timestamp — Unix time in seconds at which the delivery was signed
Recompute that HMAC with your secret over the raw request body and compare it in constant time. Verifying Webhook Signatures covers the full procedure, with Python and JavaScript handlers you can copy. Deliveries also carry the secret as plain text in the X-Webhook-Secret header. That header predates signing and is still sent so receivers built against it keep working, but prefer the signature: it proves the body was not modified, which comparing the header cannot.
Verify against the raw bytes of the request body. The handler examples above call await request.json() and read req.body, which is fine once a delivery is verified — but a parsed-and-re-serialized body produces a different signature. Capture the raw body first.

Retry Behavior

Deliveries are attempted up to 3 times in total: the initial attempt plus 2 retries, spaced roughly 10 seconds apart. Retries stop after that. Use Get Webhook to check out the full delivery history.

Best Practices

  • Return 2xx as fast as you can, then process data asynchronously
  • Implement idempotency, since you may receive the same webhook more than once
  • Log incoming payloads for debugging
  • Set a webhook_secret and verify the signature on every delivery
  • Set up monitoring for your webhook endpoint’s availability
Last modified on September 21, 2026