> ## Documentation Index
> Fetch the complete documentation index at: https://developer.opus.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Off-Platform Task

> Hand a step to your own system and let the workflow wait until your system sends the result back.

The Off-Platform Task hands a step of your workflow to a system you run. Opus posts the task's inputs to a webhook you provide, pauses the workflow, and waits. When your system has done the work, it posts the result back and the workflow carries on from where it stopped.

Use it when the work belongs somewhere else — your own operations console, a partner's system, a queue your team already watches — and you want the workflow to treat it as a normal step.

<Note>
  For a plain REST call that returns its answer immediately, use [External Service](/tasks/data/external-service) instead. That task calls an API and continues with the response. Off-Platform Task is for work that takes its own time: the workflow stays paused, sometimes for hours, until your system calls back.
</Note>

## Key Capabilities

<CardGroup cols={2}>
  <Card title="Your System, Your Rules" icon="server">
    The work happens entirely in your application. Opus only hands over the inputs and waits for the answer.
  </Card>

  <Card title="Workflow Stays Paused" icon="pause">
    The step does not time out while your system works. It resumes on your callback.
  </Card>

  <Card title="Signed Handover" icon="key">
    Every dispatch carries a single-use callback token, and you can add your own authentication headers.
  </Card>

  <Card title="Retries Built In" icon="rotate">
    Transient failures reaching your webhook are retried automatically before the step is failed.
  </Card>
</CardGroup>

## When to Use It

| Situation | Real-World Example |
| - | - |
| **Work happens in your app** | Your agents process claims in your own console, not in Opus |
| **A partner system decides** | An external adjudicator returns an approval your workflow needs |
| **Long-running work** | A batch job runs overnight and reports back in the morning |
| **Your own queue** | The task joins a work queue your team already uses |

## Setting It Up

| Setting | What It Means |
| - | - |
| **Input** | Variables from earlier in the workflow that your system receives |
| **Output** | Variables your system must send back. These become available to later steps |
| **Webhook URL** | The HTTPS endpoint Opus posts the task to. Required |
| **Webhook Headers** | Optional headers sent with every dispatch, so your endpoint can confirm the call came from Opus |
| **Timeout** | How long Opus waits for your endpoint to acknowledge the dispatch. Defaults to 15 seconds |

<Note>
  The webhook is configured per workspace when the task is connected, so the same task can point at a different endpoint in each workspace. Header values are encrypted at rest and are never shown again once saved.
</Note>

<Warning>
  Off-Platform Tasks only run for a verified organization. An unverified organization cannot create one, and if the workflow reaches one anyway, nothing is sent to your webhook and the step fails. Make sure your organization is verified before you build against it.
</Warning>

## How to Add an Off-Platform Task

<Steps>
  <Step title="Drop it into your workflow">
    Drag an Off-Platform Task from the sidebar into your workflow.
  </Step>

  <Step title="Point it at your endpoint">
    Enter the webhook URL your system listens on, and add any authentication headers your endpoint expects.
  </Step>

  <Step title="Add input variables">
    Connect the variables from earlier steps that your system needs to do the work.
  </Step>

  <Step title="Define output variables">
    Add the variables your system will send back. Later steps read these, and Opus rejects a callback that returns anything you have not declared here.
  </Step>

  <Step title="Build your endpoint">
    Accept the dispatch, do the work, and post the result to the callback URL that came with it.
  </Step>
</Steps>

## What Opus Sends

When the workflow reaches the task, Opus posts JSON to your webhook URL with your configured headers:

```json theme={null}
{
  "execution_id": "c34d1def-b73c-43a8-8ead-1669e62cc5d5",
  "workflow_id": "d3e00db5-77ea-402a-8156-0a652734b90b",
  "workflow_name": "Claim Creation",
  "inputs": {
    "correlation_id": {
      "type": "str",
      "display_name": "Correlation Id",
      "variable_name": "correlation_id",
      "value": "CORR-FIX-UCRN100411575301"
    },
    "claim_header": {
      "type": "object",
      "display_name": "Claim Header",
      "variable_name": "claim_header",
      "value": { "ucrn": "UCRN100411575301", "channel": "TP2" }
    }
  },
  "callback": {
    "url": "https://operator.opus.com/v1/execution/callback/c34d1def-b73c-43a8-8ead-1669e62cc5d5",
    "token": "lTgmnk2-twSO34sgj9CBTMhD0ASnlrlz23byrYa3ffs",
    "token_header": "X-Opus-Callback-Token"
  },
  "expected_output_schema": {
    "decision": {
      "type": "str",
      "display_name": "Decision",
      "variable_name": "decision",
      "description": "Review decision",
      "is_nullable": false
    },
    "response_text": {
      "type": "str",
      "display_name": "Response Text",
      "variable_name": "response_text",
      "is_nullable": false
    }
  }
}
```

| Field | What It Is |
| - | - |
| `execution_id` | Identifies this one execution. Log it — it ties your records to Opus's |
| `inputs` | The input variables, each with its `type` and its `value` |
| `callback` | Where to send the result, the token to send with it, and the header to put the token in |
| `expected_output_schema` | The fields Opus expects back, keyed by name. Absent when the task declares no outputs |

Acknowledge the dispatch with a `200` promptly — within the configured timeout, 15 seconds by default. This only confirms receipt. The actual result comes later, as a separate request.

<Tip>
  Store the `execution_id`, the whole `callback` block, and the `expected_output_schema` before you reply. You need them when the work finishes, and Opus will not send them again.
</Tip>

## Sending the Result Back

Post to `callback.url`, putting `callback.token` in the header named by `callback.token_header`:

```bash theme={null}
curl --request POST \
  --url https://operator.opus.com/v1/execution/callback/c34d1def-b73c-43a8-8ead-1669e62cc5d5 \
  --header 'Content-Type: application/json' \
  --header 'X-Opus-Callback-Token: lTgmnk2-twSO34sgj9CBTMhD0ASnlrlz23byrYa3ffs' \
  --data '{
    "output_data": {
      "decision": "APPROVE",
      "response_text": "Verified against the provider invoice."
    },
    "status": "success"
  }'
```

Send **bare values**. Opus knows each field's type from the output variables you declared — it appears in `expected_output_schema` — and applies it for you.

<Warning>
  Do not wrap values as `{ "value": …, "type": … }`. That wrapper is stored as the value itself, and the step resumes with data your later steps cannot read.
</Warning>

Every key in `output_data` must be one you declared. An undeclared field is rejected with `422`, naming both what you sent and what was expected — nothing is stored and the workflow stays paused, so you can correct the payload and post again.

### When the Work Cannot Be Done

Report a failure rather than leaving the workflow waiting:

```json theme={null}
{
  "status": "failed",
  "error": "Claim could not be assessed: provider record unavailable"
}
```

The execution is recorded as failed with your message.

## How It Behaves

<AccordionGroup>
  <Accordion title="If your endpoint is unreachable">
    Opus retries a failed dispatch up to 3 times with exponential backoff. Network errors and the statuses `408`, `425`, `429`, `500`, `502`, `503` and `504` are treated as transient and retried. Any other `4xx` is treated as a misconfigured webhook and fails immediately without retrying.

    After repeated failures a circuit breaker opens for 60 seconds, so a struggling endpoint is not hammered by every execution at once.
  </Accordion>

  <Accordion title="The callback token is single-use">
    The token authenticates one result for one execution. It is invalidated the moment a callback succeeds, so a replayed request is rejected with `401`.

    Keep it server-side. It is the only credential needed to complete the step, so it must never reach a browser, a log you share, or a URL.
  </Accordion>

  <Accordion title="If you call back too late">
    An execution accepts a result only while it is still waiting. Once it has been completed or closed out by its deadline, a callback returns `401` with a message saying the execution is no longer accepting callbacks — the workflow has already moved on.

    Treat that as final. Do not retry it, and show your users that the work expired.
  </Accordion>

  <Accordion title="These steps never appear in the Opus inbox">
    An off-platform step is resolved by your system, not by a person in Opus. Attempting to pick it up or complete it through the Opus human-task screens is rejected with `409`. If you want a person working inside Opus, use [Opus Human Task](/tasks/agent/opus-human-task) instead.
  </Accordion>
</AccordionGroup>

## Responses You May Get

| Status | Meaning |
| - | - |
| `200` | Result accepted. The workflow resumes |
| `401` | The token is wrong or spent, or the execution is no longer accepting callbacks |
| `404` | No execution with that id |
| `422` | `output_data` contains a field the task does not declare |

## Tips for Better Results

<AccordionGroup>
  <Accordion title="Acknowledge first, work afterwards">
    Reply `200` to the dispatch as soon as you have stored it, then do the work asynchronously. Fetching related data or rendering a screen before you reply risks blowing the timeout and triggering a retry, which leaves you handling the same execution twice.
  </Accordion>

  <Accordion title="Declare every field you intend to send">
    The output variables on the task are the contract. Adding a field in your system without adding it to the task turns a working integration into a `422`, so change the task first.
  </Accordion>

  <Accordion title="Log the execution id on both sides">
    Every dispatch and every result is tied to one `execution_id`. Logging it in your system makes a specific execution traceable end to end when something looks wrong.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Off-Platform Review" icon="clipboard-check" href="/tasks/off-platform-review">
    Send an earlier step's output out for review instead of dispatching arbitrary inputs.
  </Card>

  <Card title="External Service" icon="plug" href="/tasks/data/external-service">
    Call a REST API and continue immediately with its response.
  </Card>

  <Card title="Opus Human Task" icon="user-pen" href="/tasks/agent/opus-human-task">
    Have a person complete the work inside Opus.
  </Card>

  <Card title="Review Task" icon="check-to-slot" href="/tasks/review">
    Accept or reject a step's output inside Opus.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.