---
title: "Workflow record callback"
method: POST
path: "batchWorkflowEvent"
tags: ["Batches"]
---

# Workflow record callback

`POST batchWorkflowEvent` (webhook)

The events OpenRelay POSTs to a workflow record's callback, and the answers it accepts: the next requests to run, a pause, or the outcome.
A workflow record is a batch record whose body carries openrelay.workflow. OpenRelay POSTs an event to its workflow.url, runs the requests the answer returns, and POSTs their results as the next event, until the callback answers completed or failed.
The URL is the record's own, not a registered webhook. The request carries the record's workflow.authorization value verbatim as its Authorization header, and Content-Type application/json; it is not signed.
Each event is delivered up to four times, with pauses of 1, 2 and 3 seconds between attempts and each attempt bounded by callback_timeout_ms. A timeout, a connection error, a 429 or a 5xx answer is retried, and so is a redirect, which is never followed. When all four attempts fail, the record fails with workflow_callback_unreachable and the rest of the batch keeps running. Any other non-2xx answer fails the record with workflow_callback_rejected.

## Payload

- BatchWorkflowEvent: One event OpenRelay POSTs to a workflow record's workflow.url, with the record's workflow.authorization value as the Authorization header. A serialized event is at most 2 MiB (2097152 bytes), context and results included; when a step's results would make it larger, the event is not sent and the record fails with workflow_invalid_response, with the requests that ran still billed.
  - `attempt` integer, required: Always step + 1. It does not count deliveries: a retried delivery of an event carries the same attempt.
  - `context` unknown
  - `event` 'start' | 'results' | 'wait', required: start is the record's first event. results follows a continue answer: every request of that step finished, and results holds their outcomes. wait follows a wait answer, once the pause is over.
  - `event_id` string, required: Deterministic id of this event. A retried delivery, or the same event sent again after a batch worker resumes the record from its last saved step, carries the same event_id, so key your state on it and give a repeated event the same answer.
  - `record_id` string, required: The record's custom_id.
  - `results` BatchWorkflowResult[]: On a results event: one entry per request of the previous step, in the order the answer returned them. Absent on start and wait events.
    - `attempt` integer, required: The request's attempt, 1 when it set none or a value below 1.
    - `body` unknown
    - `code` string: Why the request failed; absent when it succeeded. A refused request carries the code of the first rule it broke, with status_code 400. A request that ran and failed carries request_failed (the inference failed, or the model answered with an error), request_time_limit (it passed the batch's 30 minute limit per model call and was stopped, not retried and not billed), tool_execution_failed (a call to its tool executor failed), response_too_large (its response passed 64 MiB), or invalid_body (its body could not be prepared for the model).
    - `custom_id` string, required: The request's custom_id, empty when it sent none.
    - `id` string, required: The request's id, or the one OpenRelay generated for it.
    - `message` string: Why the request failed, in words; absent when it succeeded. A refused request's message is at most 1024 bytes once JSON-encoded, ending in ... when it was cut; a request that ran and failed has one of at most 1000 bytes, cut on a character boundary.
    - `stage_id` string: The request's stage_id, else its custom_id. Absent when both are empty.
    - `status_code` integer, required: 2xx when the request succeeded. 400 when it broke a rule. The status of OpenRelay's error when the model refused the request, for example 400 for a prompt longer than its context. The status of the OpenRelay gateway answer it failed on, such as a 503 that kept coming, when it ran through the gateway. 500 when the inference failed otherwise.
    - `usage` object: Tokens this request used. Present when the request's final model call answered, as for body; after tool rounds it is that last answer's usage alone. Absent on a refused request and on one that failed without a final answer (tool_execution_failed, invalid_body, request_time_limit, response_too_large, or request_failed without a model answer). A failed request's tokens are not billed.
      - `input_tokens` integer, required
      - `output_tokens` integer, required
  - `step` integer, required: Callback answers of continue or wait consumed so far, starting at 0.
  - `workflow_id` string, required: The batch id and the record's custom_id, joined as <batch_id>:<custom_id>.

## Acknowledgement `2XX`

The callback's answer.

- BatchWorkflowAnswer: Your callback's answer to an event: the JSON body of a 2xx response, at most 4 MiB (4194304 bytes). An answer that is not JSON, is over 4 MiB, gives state, requests (not an array), retry_after_ms or error the wrong JSON type whatever the state, names an unknown state, or breaks a rule of its state fails the record with workflow_invalid_response. output is read only for completed; a field the state does not use is otherwise ignored.
  - `error` string: For failed: why the record failed, required and not blank. The record's error line carries it as the message, trimmed and cut to 1000 bytes.
  - `output` object: For completed: the record's result body, a JSON object (null is not one). OpenRelay sets its usage key to the record's prompt_tokens, completion_tokens and total_tokens, summed over its requests, replacing any usage the object carries.
  - `requests` BatchWorkflowRequest[]: For continue: 1 to 32 requests, run concurrently as this step. None, or more than 32, fails the record with workflow_invalid_response, and so does any request on a wait answer. A request that breaks a rule does not fail the record: it alone is refused, and its outcome comes back in the next results event.
    - `attempt` integer: Your retry counter for this request, echoed on its result. Omitted or below 1, it is 1.
    - `body` object, required: The request body, as for the batch's endpoint, at most 2 MiB (2097152 bytes). Missing or null, it is refused with missing_body; one that is not a JSON object, is over 2 MiB, or repeats a reserved key such as openrelay, with invalid_body. It may omit model or name the batch's model (model_mismatch otherwise), cannot set "stream": true (streaming_unsupported), and cannot carry openrelay.workflow (invalid_workflow). It may carry openrelay.tool_config, whose tool rounds are part of this one request.
    - `custom_id` string, required: Your name for this request, echoed on its result. Missing, it is refused with missing_custom_id. Every request that shares its custom_id with another of the step is refused with duplicate_custom_id.
    - `id` string: Your stable id for this request, echoed on its result. Omitted, OpenRelay generates <workflow_id>:step:<step>:req:<index>, index counting from 0 in the answer's order. Every request that shares its id with another of the step, whatever their attempts, is refused with duplicate_request_id. When an event is sent again and your answer repeats a request's id and attempt, OpenRelay reuses the result it already has instead of running and billing the request again.
    - `method` string: POST, in any letter case, when present; any other method is refused with invalid_method.
    - `stage_id` string: Your label for the stage this request belongs to, echoed on its result. Defaults to custom_id.
    - `url` string: The batch's endpoint, such as /v1/chat/completions, when present; any other value is refused with invalid_url.
  - `retry_after_ms` integer: For wait: the pause before the wait event, in milliseconds. Omitted, 0 or negative waits 1000; a value over 60000 waits 60000.
  - `state` 'continue' | 'wait' | 'completed' | 'failed': continue (also when state is omitted): run requests, then send a results event. wait: pause for retry_after_ms, then send a wait event. completed: the record succeeds with output as its result body. failed: the record fails with workflow_failed. Each continue or wait answer is one step of max_steps.

## Other responses

- `429`: Retried, as one of the event's four delivery attempts.
- `5XX`: Retried, as one of the event's four delivery attempts.
- `default`: Any other status fails the record with workflow_callback_rejected. A redirect is the exception: it is not followed and is retried as a failed attempt.

---

[API](https://skmtc.dev/openrelay/apis/openrelay-api.md) - [All operations](https://skmtc.dev/openrelay/apis/openrelay-api/llms.txt) - [OpenAPI document](https://skmtc.dev/openrelay/apis/openrelay-api/revisions/7fe59d94a9f5?raw)
