Skip to main content
Some agent tasks run too long to hold an HTTP connection open. Instead of waiting, you supply Scout with a callback URL — Scout starts the task, returns immediately, and POSTs the result to your endpoint when the agent finishes.

When to Use Async

  • Long-running tasks — anything that runs longer than 30 seconds, such as order processing, report generation, or bulk data workflows
  • Unreliable connections — environments where connections time out or drop before a synchronous response can return
  • Queued workflows — cases where you enqueue tasks and handle their results separately

How It Works

1

Start the interaction

Provide a callback_url when starting the interaction.
2

Get an immediate response

Scout responds right away with 202 Accepted and a session_id.
3

The agent runs in the background

Scout runs the agent task without holding your connection open.
4

Receive the result

On completion, Scout POSTs the result to your callback URL.

API Reference

Start Async Interaction

Start an interaction that runs in the background and reports its result to a callback URL.
POST https://api.scoutos.com/v1/agents/{agent_id}/interact
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

Parameters

message
string
required
The instruction or input for the agent to act on.
callback_url
string
required
An HTTPS URL where Scout POSTs the result when the agent finishes. Must be publicly reachable — see Requirements.

Request

{
  "message": "Process all pending orders and send confirmation emails",
  "callback_url": "https://your-app.com/webhooks/scout-callback"
}

Response

Scout returns 202 Accepted immediately, before the agent runs.
session_id
string
The identifier for this agent session. Use it to correlate the callback with the request that started it.
events_url
string
The URL to fetch the full event stream for this session once it completes.
{
  "session_id": "sess_abc123",
  "events_url": "https://api.scoutos.com/v1/agent-sessions/sess_abc123/events"
}

Callback Payload

When the agent finishes, Scout POSTs a JSON payload to your callback_url.
callback_event_id
string
A unique identifier for this callback delivery. Use it to deduplicate retries — see Retry Behavior.
session_id
string
The session this callback reports on, matching the session_id from the original response.
status
string
Either succeeded or failed.
completed_at
string
ISO 8601 timestamp of when the agent finished.
events_url
string
The URL to fetch the full event stream for the session.
error
object
Present only when status is failed. Contains a code and a human-readable message.
{
  "callback_event_id": "2b2f5b7d-7a5d-4f9b-9f6d-8ed0d2c7a1d2",
  "session_id": "sess_abc123",
  "status": "succeeded",
  "completed_at": "2026-03-05T14:30:00Z",
  "events_url": "https://api.scoutos.com/v1/agent-sessions/sess_abc123/events"
}

Fetching Results

The callback payload confirms completion but doesn’t include the agent’s full output. Use the events_url to retrieve the complete event stream:
GET https://api.scoutos.com/v1/agent-sessions/sess_abc123/events
Authorization: Bearer YOUR_API_KEY

Callback Authentication

Every callback includes signature headers so you can confirm the request genuinely came from Scout:
X-Scout-Signature-Alg: HMAC-SHA256
X-Scout-Signature: t=1709651400,sig=base64-encoded-signature

Verifying the Signature

1

Parse the header

Extract t (timestamp) and sig (signature) from the X-Scout-Signature header.
2

Build the signature base string

Concatenate the timestamp and the raw request body as {timestamp}.{raw_request_body}.
3

Compute the HMAC

Compute HMAC-SHA256 over the base string using your org secret key.
4

Compare

Compare your computed value against sig using a constant-time comparison.
import hmac
import hashlib
import base64

def verify_scout_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    # Parse the header: t=1709651400,sig=base64...
    parts = dict(item.split("=", 1) for item in signature_header.split(","))
    timestamp = parts.get("t", "")
    provided_sig = parts.get("sig", "")

    # Build the signed string
    signed_string = f"{timestamp}.{raw_body.decode('utf-8')}"

    # Compute HMAC-SHA256
    expected = hmac.new(
        secret.encode("utf-8"),
        signed_string.encode("utf-8"),
        hashlib.sha256
    ).digest()
    expected_b64 = base64.b64encode(expected).decode("utf-8")

    # Constant-time comparison prevents timing attacks
    return hmac.compare_digest(expected_b64, provided_sig)

Example Webhook Handler (Express)

const express = require("express");
const crypto = require("crypto");
const app = express();

// Use raw body for signature verification
app.post("/webhooks/scout-callback", express.raw({ type: "application/json" }), async (req, res) => {
  const sigHeader = req.headers["x-scout-signature"];

  if (!verifyScoutSignature(req.body, sigHeader, process.env.SCOUT_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const payload = JSON.parse(req.body);

  // Deduplicate using callback_event_id
  if (await alreadyProcessed(payload.callback_event_id)) {
    return res.status(200).send("Already handled");
  }

  if (payload.status === "succeeded") {
    // Fetch the full event stream if you need step-by-step details
    await handleSuccess(payload.session_id, payload.events_url);
  } else {
    await handleFailure(payload.session_id, payload.error);
  }

  // Respond within 10 seconds or Scout will retry
  res.status(200).send("OK");
});
Always read the raw request body before calling JSON.parse. If the JSON is parsed and re-serialized first, the body bytes change and the signature won’t match.

Retry Behavior

Scout uses at-least-once delivery, so the same callback may arrive more than once.
  • Deduplication — check callback_event_id before processing to avoid duplicate work
  • Retry schedule — exponential backoff over roughly 24 hours
  • Retry triggers — network errors or 5xx responses from your endpoint

Requirements

  • callback_url must use HTTPS
  • Private and internal URLs aren’t supported (SSRF protection)
  • Your endpoint must respond within 10 seconds