03 External callbacks intro
Before you start
Complete 02 Server API first run so the server stack, Bearer auth, and Interrupted run status are familiar. External callbacks require Postgres with recovery_enabled: true, same as HITL.
Read External callbacks when you need full JSON schema fields, recovery policies, and production HMAC setup.
Concept
Some workflow steps delegate work outside the Rust engine: browser automation, payment processors, legacy APIs, or manual portals. ArcFlow models this with external bindings declared on the workflow definition. When the attached step finishes agent work, the engine emits ExternalBindingStarted, sets run status to Interrupted, and waits for a signed HTTP callback.
The integrator performs external work, then reports the outcome. Documentation calls this operation ExternalOutcome.report. The Python SDK exports the helper as report_outcome from arcflow.external. There is no Python type named ExternalOutcome; the server accepts ExternalOutcomeReport JSON.
Typical lifecycle:
- Engine executes the bound step (agent, tools, memory).
- Run moves to
Interruptedwith external binding metadata. - Integrator completes external work (Playwright, webhook handler, batch job).
- Integrator calls
POST /v1/runs/{run_id}/external/{binding_id}with signed outcome JSON. - Engine validates the outcome schema, applies recovery policy, and resumes or fails the run.
Runs with external bindings need exec_config.recovery_enabled: true (Python: Workflow.enable_recovery()).
Example
Minimal Python workflow with one binding (server must be running):
When the run pauses, report success with ExternalOutcome.report (report_outcome in code):
Outcome status values:
| status | Meaning |
|---|---|
success | External work succeeded; engine resumes |
failed | Fatal external error; recovery policy applies |
needs_input | More input required; may trigger agent re-ask |
Required environment variables for the callback helper:
| Variable | Purpose |
|---|---|
ARCFLOW_SERVER_API_KEY | Bearer auth on callback POST |
ARCFLOW_WEBHOOK_SECRET | HMAC signing secret (must match server) |
If ARCFLOW_WEBHOOK_SECRET is unset on the server, external callback routes return 503.
See examples/external/playwright_stub_callback.py for a CLI stub and examples/static/online-application-chatbot/ for a static product pattern.
Verify
| Check | Expected |
|---|---|
Run with binding reaches Interrupted | Yes, after bound step completes |
report_outcome with valid HMAC | Run resumes toward Completed |
| Trace after callback | ExternalBindingCompleted metadata event |
| Invalid signature | Rejected before outcome parsing |
Poll GET /v1/runs/{run_id} while waiting. When status is Interrupted, inspect the interrupt payload for binding context before posting the outcome.
Next
04 HITL approval intro covers human approval gates, another Interrupted pattern that uses POST /v1/runs/{run_id}/approve/{approval_key} instead of external callbacks.
Deep dives: Webhook security, Recovery and resume, Track E: HITL and external.