Async Steps

Use async steps when work outlives a single HTTP request. Covers the webhook URL format, the receipt token idempotency model, and the safe retry pattern for completion callbacks.

When to Use Async Steps

Use async steps when work is long-running, queue-based, or handled by a background worker that cannot return results within a single HTTP request.

How It Works

  1. The engine calls your endpoint and sends a Argyll-Webhook-URL header
  2. Your handler returns a 2xx immediately; it does not return outputs yet
  3. Your background worker processes the task and POSTs results to the webhook URL

The webhook URL has the form:

{WEBHOOK_BASE_URL}/webhook/{flow_id}/{step_id}/{receipt_token}

The engine holds the Flow open until the callback arrives.

{
  "id": "process-payment",
  "name": "Process Payment",
  "type": "async",
  "http": {
    "method": "POST",
    "endpoint": "https://api.example.com/payments/capture",
    "timeout": 1000
  },
  "attributes": {
    "amount": { "role": "required", "type": "number" },
    "transaction_id": { "role": "output", "type": "string" }
  }
}

The timeout field controls only the initial HTTP request, not how long the engine waits for the callback. There is no deadline on the callback itself beyond retry configuration.

Completing Work

POST output attributes to the webhook URL on success:

{
  "transaction_id": "txn-abc123"
}

POST application/problem+json on permanent failure:

{
  "type": "about:blank",
  "title": "Payment Declined",
  "status": 422,
  "detail": "Card was declined"
}

A 4xx problem response records a permanent failure. A 5xx or network error from the webhook call is retryable, the engine will retry the step invocation.

Idempotency

Each work item carries a unique receipt token in Argyll-Receipt-Token. The engine uses this to deduplicate. If your worker posts to the same webhook URL twice, the engine ignores the duplicate and returns 200 either way, so retrying a webhook call is safe.

def complete_work(flow_id, step_id, token, result):
    url = f"https://engine/webhook/{flow_id}/{step_id}/{token}"
    for attempt in range(5):
        try:
            r = requests.post(url, json=result, timeout=10)
            if r.status_code == 200:
                return  # success (engine accepted, or this is a duplicate)
            if 400 <= r.status_code < 500:
                # invalid flow/step/token, or malformed body, do not retry
                raise PermanentError(r.text)
            # 5xx or unexpected, fall through to retry
        except requests.RequestException:
            pass
        time.sleep(2 ** attempt)

Execution Context Headers

All HTTP steps receive these headers:

HeaderValue
Argyll-Flow-IDThe Flow ID
Argyll-Step-IDThe Step ID
Argyll-Receipt-TokenUnique token for this Work Item
Argyll-Webhook-URLCallback URL (async steps only)

Log the Flow ID, Step ID, and token in your worker for traceability.