Seedance 2.5 is live — 30-second cinematic video with native audio & real-person references
rreAPI Docs

Webhooks

Receive a POST when a task finishes instead of polling — request field, delivery contract, retries, and how to verify calls.

Every submission endpoint accepts an optional webhook_url. When the task reaches a terminal state — completed or failed — reAPI sends one POST to that URL with the same JSON you would get from GET /api/v1/tasks/{id}. Polling keeps working exactly as before; the webhook is an addition, not a replacement.

GET /api/v1/tasks/{id} is always the fallback. A webhook is a notification, not the only copy of the result. If your endpoint is down, slow, or returns a non-2xx, reAPI retries a few times and then stops — but the task result is never lost: it stays available through GET /api/v1/tasks/{id} at any time, and ?include=webhook tells you whether the delivery succeeded. Build your integration so that a missed webhook degrades into a poll, never into a missing result (see Retries and duplicates).

Requesting a webhook

Add webhook_url next to the model parameters on any of these endpoints: /api/v1/images/generations, /api/v1/videos/generations, /api/v1/audio/generations, /api/v1/detect, /api/v1/humanize, /api/v1/essay, /api/v1/moderations.

POST /api/v1/images/generations
Authorization: Bearer rk_live_xxx
Content-Type: application/json

{
  "model": "z-image",
  "prompt": "A red fox running through fresh snow at dawn",
  "webhook_url": "https://hooks.example.com/reapi"
}

Everything except webhook_url is the model's normal request; the field is simply added to whatever you already send.

Rules for the URL:

RuleDetail
https onlyhttp:// and any other scheme are rejected
Hostname onlyThe host must be a domain name; IP addresses (IPv4 or IPv6, in any notation) are rejected
Port 443 onlyAn explicit port is accepted only if it is 443 (https://hooks.example.com:8443/x is rejected)
No credentialshttps://user:pass@host/ is rejected
LengthAt most 2048 characters

These are checked at submission: a URL that breaks one of them returns a 20003 parameter error and no task is created. Whether the endpoint is reachable is not checked at submission.

One more rule applies at delivery, when the name is resolved: if it resolves to a private, loopback, link-local or cloud-metadata address (any of its records), the attempt is refused and recorded as blocked_private_address. The task itself is created and runs normally — only the notification is affected, and GET /api/v1/tasks/{id} still has the result.

The delivery

POST https://hooks.example.com/reapi
Content-Type: application/json
User-Agent: reAPI-Webhook/1

There are no reAPI-specific headers: the task is identified by the id field in the body, and nothing else about your account travels with the call. If you use several API keys and need to tell their tasks apart, give each key its own webhook_url.

The body is exactly the default GET /api/v1/tasks/{id} response for that task — same fields, same shapes, nothing added — with status always completed or failed. Its id is the same task id the submission POST returned, so you always know which task a call belongs to:

{
  "id": "task_01a093d0162671be8280f65bc79becff",
  "model": "z-image",
  "status": "completed",
  "created_at": 1789186286,
  "output": {
    "image_urls": ["https://cdn.reapi.ai/media/tasks/task_01a093d0162671be8280f65bc79becff/0.jpg"]
  },
  "usage": { "credits": 5 },
  "error": null
}

A failed task arrives the same way, with output: null, usage.credits at the final charge (0 when the reservation was fully refunded), and the same error object polling would return. The body never includes the opt-in billing block; fetch it with ?include=billing if you need exact token billing. error.request_id carries the delivery id (whd_…), useful when contacting support about a specific call.

Your endpoint should respond with any 2xx status within 10 seconds and do the real work afterwards. Redirects are not followed and the response body is never read.

Retries and duplicates

AttemptWhen
1as soon as the task finishes
2about 40 seconds later, if attempt 1 failed
3about 5 minutes after the task finished, if attempt 2 failed

A non-2xx status, a timeout, or a connection failure counts as a failed attempt. After the third failure reAPI stops. Nothing is lost when that happens: the task result stays available through GET /api/v1/tasks/{id} indefinitely, and GET /api/v1/tasks/{id}?include=webhook shows "status": "failed" with the reason. The recommended pattern:

  1. Record the task id when you submit.
  2. Handle the webhook when it arrives (deduplicate on the task id).
  3. If no webhook has arrived within the time you expect the task to take (plus the ~5 minute retry window), poll GET /api/v1/tasks/{id} for that id and continue exactly as if the webhook had come in.

Treat the webhook as the fast path and the GET as the guaranteed path.

Delivery is at-least-once. If your server processed a call but the 2xx response was lost on the network, reAPI cannot tell the difference from a failure and will send it again. Every attempt for a task carries the same body id and an otherwise identical body (apart from error.request_id), so deduplicate on the task id: if you have already handled that id, respond 2xx and ignore the payload.

Verifying that a call is from reAPI

reAPI does not sign webhooks and sends no credentials of its own. The robust pattern needs no secret at all: treat the call as a signal, not as the source of truth. Read the body's id, then fetch GET /api/v1/tasks/{id} with your API key and act on that response. The GET is authenticated, so a forged call cannot forge a task result.

A forged call can still make you do a lookup, so keep the lookups cheap: only act on task ids you recorded as pending when you submitted them and drop anything else without a request; treat a repeated id as one event; and do not let a burst of calls fan out into a burst of GETs — the polling endpoint is rate-limited per user, and a flood of lookups could throttle your own legitimate requests.

If you would rather trust the payload directly, put a secret of your own into the URL — a query parameter or a path segment, for example https://hooks.example.com/reapi?token=<random value you generate> — and check it on your side. The URL is called back verbatim, so whatever you embed arrives unchanged; you generate, store, and rotate that value.

Checking a delivery

GET /api/v1/tasks/{id}?include=webhook
Authorization: Bearer rk_live_xxx

Adds a webhook object to the task response:

{
  "webhook": {
    "status": "failed",
    "attempts": 2,
    "response_status": 503,
    "last_error": "http_503",
    "last_attempt_at": "2026-09-12T10:31:02.118Z",
    "delivered_at": null
  }
}

status is pending (task not finished yet), in_progress, delivered, or failed. last_error is one of http_<status>, timeout, connect_failed, dns_failed, tls_error, blocked_private_address, request_failed, render_timeout, render_failed (reAPI could not build the payload in time — the task result itself is unaffected), stale_claim (an attempt was abandoned because it could no longer finish inside its window), or attempt_lost (the last attempt died before it could report; nothing more will be sent — poll the task). webhook is null when the task was submitted without a webhook_url. Without ?include=webhook the task response is unchanged.

Table of Contents