Skip to Content
🎉 Scout Docs 2.0 is here!
AgentsAsync Interactions

Asynchronous Agent Interactions

Some agent tasks take longer than a single HTTP connection can reliably hold open. Instead of keeping a connection alive for minutes while your agent works, you give Scout a callback URL. Scout fires off the task, returns immediately, and POSTs the result to your endpoint when it’s done.

When to Use Async

  • Tasks that run longer than 30 seconds (order processing, report generation, bulk data workflows)
  • Environments where connections drop or time out unpredictably
  • Any workflow where you need to queue tasks and handle results separately

How It Works

  1. You provide a callback_url when starting the interaction
  2. Scout responds immediately with 202 Accepted and a session_id
  3. The agent runs in the background
  4. When complete, Scout POSTs to your callback URL with the result

API Reference

Start Async Interaction

Request

POST /v1/agents/{agent_id}/interact Content-Type: application/json { "message": "Process all pending orders and send confirmation emails", "callback_url": "https://your-app.com/webhooks/scout-callback" }

Response (202 Accepted)

{ "session_id": "sess_abc123", "events_url": "https://api.scoutos.com/v1/agent-sessions/sess_abc123/events" }

Callback Payload

When the agent completes, Scout POSTs to your callback_url:

Success

{ "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" }

Failure

{ "callback_event_id": "2b2f5b7d-7a5d-4f9b-9f6d-8ed0d2c7a1d2", "session_id": "sess_abc123", "status": "failed", "completed_at": "2026-03-05T14:30:00Z", "events_url": "https://api.scoutos.com/v1/agent-sessions/sess_abc123/events", "error": { "code": "EXECUTION_ERROR", "message": "Agent exceeded maximum step count" } }

Fetching Results

Use the events_url to retrieve the full 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 it came from Scout:

X-Scout-Signature-Alg: HMAC-SHA256 X-Scout-Signature: t=1709651400,sig=base64-encoded-signature

Verifying the Signature

  1. Extract t (timestamp) and sig (signature) from the header
  2. Build the signature base string: {timestamp}.{raw_request_body}
  3. Compute HMAC-SHA256 using your org secret key
  4. Compare your result to sig using a constant-time comparison

Here’s how to do it in Python and Node.js:

Python

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)

Node.js

const crypto = require("crypto"); function verifyScoutSignature(rawBody, signatureHeader, secret) { // Parse the header: t=1709651400,sig=base64... const parts = Object.fromEntries( signatureHeader.split(",").map((p) => p.split("=", 2)) ); const timestamp = parts.t ?? ""; const providedSig = parts.sig ?? ""; // Build the signed string const signedString = `${timestamp}.${rawBody}`; // Compute HMAC-SHA256 const expected = crypto .createHmac("sha256", secret) .update(signedString) .digest("base64"); // Constant-time comparison prevents timing attacks return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(providedSig) ); }

Example Webhook Handler (Express)

A minimal endpoint that verifies the signature and handles the callback:

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" }), (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"); });

Important: Always parse the raw request body before calling JSON.parse. If your framework parses JSON first, the body bytes change and the signature won’t match.

Retry Behavior

Scout uses at-least-once delivery, meaning the same callback can arrive more than once.

  • Deduplication: Check callback_event_id before processing to avoid duplicate work
  • Retry schedule: Exponential backoff over ~24 hours
  • Retry triggers: Network errors or 5xx responses from your endpoint

Requirements

  • callback_url must be HTTPS
  • Private/internal URLs aren’t supported (SSRF protection)
  • Your endpoint must respond within 10 seconds
Last updated on