External callbacks

External bindings let a workflow wait for work outside the Rust engine: browser automation, payment webhooks, scheduled jobs, or custom integrators. When a bound step completes, the runtime emits ExternalBindingStarted, sets the run to Interrupted, and waits for a signed callback on the server.

This guide covers workflow configuration with ExternalBindingConfig, the callback lifecycle, and reporting outcomes with ExternalOutcome.report (Python SDK symbol: report_outcome).

Workflow-level bindings

Declare bindings on the workflow definition, not on individual agents:

json
{
 "external_bindings": [
 {
 "id": "payment_webhook",
 "kind": "http_callback",
 "attach_to_step_id": "00000000-0000-4000-8000-000000000050",
 "mode": "async_callback",
 "outcome_schema": {
 "type": "object",
 "properties": {
 "status": { "enum": ["success", "failed", "needs_input"] }
 },
 "required": ["status"]
 },
 "recovery": {
 "max_retries": 3,
 "on_needs_input": "agent_reask",
 "on_fatal": "fail_run"
 }
 }
 ]
}
FieldPurpose
idBinding identifier in callback URL and traces
kindIntegrator type (browser_automation, schedule_trigger, custom, or http_callback in JSON payloads)
attach_to_step_idStep UUID that triggers the wait after step agent work
modeasync_callback (default) waits for HTTP callback; sync_tool completes in-process
outcome_schemaJSON Schema for callback body validation
recoveryPolicy when outcome is failed or needs_input

Runs with external bindings require recovery_enabled: true and Postgres, same as HITL.

Python: ExternalBindingConfig

python
from arcflow import Workflow, Agent
from arcflow.external import ExternalBindingConfig, report_outcome

STEP_ID = "550e8400-e29b-41d4-a716-446655440000"

binding = ExternalBindingConfig(
 "gov_portal_submit",
 attach_to_step_id=STEP_ID,
 kind="browser_automation",
 mode="async_callback",
 recovery={
 "max_retries": 2,
 "on_needs_input": "agent_reask",
 "on_fatal": "hitl_escalate",
 },
)

wf = Workflow("online_application", runtime="http://localhost:8080")
wf.enable_recovery()
wf.add_external_binding(binding)
wf.step(Agent(name="applicant", role="user", instructions="Complete the form."))

Publish the workflow to the server with external_bindings included in the workflow JSON sent to POST /v1/runs, or register via the workflow registry when using workflow_ref.

Runtime lifecycle

  1. Engine executes the attached step (agent invocation, tools, memory).
  2. On step completion, engine emits ExternalBindingStarted and sets run status Interrupted.
  3. Integrator performs external work (Playwright, payment API, human form).
  4. Integrator calls POST /v1/runs/{run_id}/external/{binding_id} with signed ExternalOutcomeReport JSON.
  5. Engine validates schema, applies recovery policy, emits ExternalBindingCompleted or ExternalBindingFailed, and resumes or fails the run.

Trace events are metadata-only (binding id, duration, error codes). See trace event reference.

ExternalOutcome.report

Post outcomes from integrator code with the SDK helper exported as report_outcome from arcflow.external (also re-exported from arcflow). Documentation refers to this operation as ExternalOutcome.report.

python
from arcflow.external import ExternalBindingConfig, report_outcome

# ExternalOutcome.report
response = report_outcome(
 "550e8400-e29b-41d4-a716-446655440000",
 "gov_portal_submit",
 {
 "status": "success",
 "fields": {"confirmation_number": "APP-2026-9912"},
 },
 base_url="http://localhost:8080",
)
print(response)

Outcome statuses:

statusMeaning
successExternal work succeeded; engine resumes workflow
failedFatal external error; recovery policy applies
needs_inputMore user input required; may trigger agent re-ask

Optional fields: error_code, fields (object), artifact_refs (string array).

Requires environment variables:

VariablePurpose
ARCFLOW_SERVER_API_KEYBearer auth on callback POST
ARCFLOW_WEBHOOK_SECRETHMAC signing secret (must match server)

See examples/external/playwright_stub_callback.py for a CLI stub.

HTTP callback (without SDK)

bash
BODY='{"binding_id":"payment_webhook","status":"success","fields":{"transaction_id":"tx_123"}}'
SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$ARCFLOW_WEBHOOK_SECRET" | awk '{print $2}')"

curl -sS -X POST "http://localhost:8080/v1/runs/${RUN_ID}/external/payment_webhook" \
 -H "Content-Type: application/json" \
 -H "Authorization: Bearer ${ARCFLOW_SERVER_API_KEY}" \
 -H "X-ArcFlow-Signature: ${SIG}" \
 -d "$BODY"

Send X-Idempotency-Key on retries; duplicate keys return 202 with already_processed.

Async vs sync modes

ModeBehavior
async_callbackStep completes in engine; run waits at Interrupted until callback
sync_toolExternal work modeled as in-process tool completion (no HTTP wait)

Production integrations (Playwright workers, payment processors) typically use async_callback.

Recovery policy

When status is failed or needs_input, recovery controls the next action:

FieldValuesEffect
max_retriesintegerRetry external attempt; emits ExternalRecoveryTriggered
on_needs_inputagent_reask, fail_runRe-enter agent loop or fail
on_fatalhitl_escalate, fail_runEscalate to HITL or fail run