ReluAutomate
Guides

Poll vs webhook

How to know when an order is done, the two options and when to use each.

Orders run asynchronously. After you create an order, there are two ways to know when it reaches a terminal state (Done, Failed, or Cancelled):

  1. Webhook, Relu calls your server when the order is done.
  2. Polling, you request the status of one or more orders until each reaches a terminal state.

Both are supported. For production, use webhooks as the primary path + infrequent polling as a safety net.

Webhooks

Pass callback_url when creating the order:

{
  "order_name": "...",
  "input_data": { "...": "..." },
  "callback_url": "https://your-app.com/relu-webhook"
}

Relu POSTs to that URL when the order reaches a terminal state. The body includes the event type and the order:

{
  "event_type": "order.job_status",
  "order_id": "<order_id>",
  "order_name": "<order_name>"
}

See Callbacks for all event types and retry behavior.

Pros:

  • Real-time, you react immediately when the order is done, no wasted polling requests.
  • Fewer API calls on both sides.
  • Surfaces non-terminal events too (order.status approval changes, order.outputs_edited, order.unlock), richer signals than polling.

Cons:

  • Needs a publicly reachable endpoint.
  • Callbacks can fail if your server is down or slow. Relu retries with exponential backoff for up to 24 hours, then drops the callback — pair with infrequent polling as a safety net for outages longer than that. Because retries can deliver the same event more than once, treat your handler as idempotent and dedup on the X-Relu-Delivery-Id header. See Idempotency in Callbacks.

Every callback is signed with Ed25519 (X-Relu-Signature + X-Relu-Signature-Timestamp headers). Verification only needs the public keys we publish at /.well-known/webhook-keys.json — there is no shared secret to provision. See the Security section in Callbacks for example code.

Polling

POST https://api.automate.relu.ai/orders/job-statuses
Content-Type: application/json

{ "order_ids": ["<order_id_a>", "<order_id_b>"] }

The response contains the latest Main job for each accessible order:

{
  "statuses": {
    "<order_id_a>": {
      "order_id": "<order_id_a>",
      "main_job": {
        "job_status": "Submitted",
        "end_timestamp": null
      }
    }
  },
  "errors": [
    {
      "order_id": "<missing_or_inaccessible_order_id>",
      "code": "not_found_or_not_accessible"
    }
  ]
}

Each accessible order appears in statuses. Its main_job is null until processing starts. Missing and inaccessible orders appear in errors instead. Handle each entry separately so one missing order does not stop the rest of a reconciliation batch.

When main_job is present, check main_job.job_status for "Submitted", "Done", "Failed", or "Cancelled". The endpoint accepts between 1 and 100 valid order IDs and does not query the processing engines. An empty list, more than 100 IDs, or a malformed ID returns 422 for the whole request. If you need a percentage, use POST /orders/progress instead.

Use the status-only endpoint unless your UI needs a progress bar. It reads from Automate's database and does not wait for a processing engine. The progress endpoint may query one or more processing engines when its cached values are stale, so it can take longer and uses more server resources.

Pros:

  • No infrastructure required, any backend can poll.
  • Works for local development and ad-hoc scripts.
  • Deterministic: you control the cadence.

Cons:

  • Wasteful at scale, most polls return "still running".
  • Latency is bounded by your poll interval.

Cadence

Don't poll faster than every 30 seconds in production. Most services take minutes, not seconds. For long-running services (implant planning), every 5–10 minutes is more appropriate.

Polling more often does not make an order finish sooner. Use webhooks when you need a notification as soon as processing finishes.

Best-practice integrations use webhooks for the primary signal and polling as a safety net:

  1. Pass callback_url when creating the order.
  2. In your handler, mark the order as "complete" when the webhook arrives.
  3. Every 30 minutes, request the statuses of locally unconfirmed orders that have not reached a terminal state. Split requests into batches of at most 100 IDs. Reconcile each status or error independently.

This handles webhook failures (your server was down during a callback) without the cost of aggressive polling.

  • Callbacks, event types and retry behavior
  • Orders, the lifecycle being observed

On this page