Resources

Webhooks

Floo POSTs JSON events to your endpoint as calls progress. Configure the URL, secret, and event subscriptions per agent from the dashboard.

Headers we send

  • Content-Type: application/json
  • User-Agent: FlooAI-Webhook/1.0
  • X-Floo-Event - the event name, e.g. call_ended
  • X-Floo-Timestamp - ISO 8601 send time
  • X-Floo-Signature - sha256=<hex> HMAC of the raw body (only when a secret is configured)

Signature verification

We sign every request with HMAC-SHA256 using the agent's webhook secret. Recompute the HMAC server-side from the raw body and compare in constant time.

Node - verify Floo signaturets
700 font-semibold">import crypto 700 font-semibold">from "node:crypto";

700 font-semibold">export 700 font-semibold">function verifyFloo(
  rawBody: string,
  signatureHeader: string | 700 font-semibold">null,
  secret: string,
): boolean {
  700 font-semibold">if (!signatureHeader) 700 font-semibold">return 700 font-semibold">false;
  700 font-semibold">const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  700 font-semibold">const provided = signatureHeader.replace(/^sha256=/, "");
  700 font-semibold">const a = Buffer.700 font-semibold">from(expected, "hex");
  700 font-semibold">const b = Buffer.700 font-semibold">from(provided, "hex");
  700 font-semibold">if (a.length !== b.length) 700 font-semibold">return 700 font-semibold">false;
  700 font-semibold">return crypto.timingSafeEqual(a, b);
}
Python - verify Floo signaturepy
700 font-semibold">import hmac
700 font-semibold">import hashlib

700 font-semibold">def verify_floo(raw_body: bytes, signature_header: str, secret: str) -> bool:
    700 font-semibold">if 700 font-semibold">not signature_header:
        700 font-semibold">return 700 font-semibold">False
    expected = hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    provided = signature_header.removeprefix("sha256=")
    700 font-semibold">return hmac.compare_digest(expected, provided)

Retry policy

We expect a 2xx response within 5 seconds. Any 5xx or network/timeout failure is retried once after a 5-minute delay; non-2xx 4xx responses are marked failed and not retried (your endpoint rejected it for a reason). Every attempt is logged in the dashboard and replayable from the deliveries log.

Events

EVENTcall_started

Fired when an agent connects to a caller. Use to log the call into your CRM and start any real-time UI.

Payload

JSON
{
  "event": "call_started",
  "agent_id": "agt_2k9f1a",
  "workspace_id": "ws_4p2j1k",
  "timestamp": "2026-05-15T17: 55: 00Z",
  "data": {
    "call_id": "call_z8h2k1",
    "direction": "inbound",
    "from_number": "+15551234567",
    "to_number": "+15557654321",
    "started_at": "2026-05-15T17: 55: 00Z"
  }
}
EVENTcall_ended

Fired when a call disconnects for any reason. Includes duration and end_reason. Sentiment may be null if extraction has not yet completed.

Payload

JSON
{
  "event": "call_ended",
  "agent_id": "agt_2k9f1a",
  "workspace_id": "ws_4p2j1k",
  "timestamp": "2026-05-15T17: 56: 27Z",
  "data": {
    "call_id": "call_z8h2k1",
    "duration_seconds": 87,
    "end_reason": "hangup",
    "sentiment": "positive",
    "successful": true
  }
}
EVENTtranscript_turn

Fired for each finalized turn (one per speaker change). High volume - only subscribe if you need live transcription.

Payload

JSON
{
  "event": "transcript_turn",
  "agent_id": "agt_2k9f1a",
  "workspace_id": "ws_4p2j1k",
  "timestamp": "2026-05-15T17: 55: 03Z",
  "data": {
    "call_id": "call_z8h2k1",
    "role": "user",
    "text": "Hello, I'd like to book an appointment.",
    "started_at_ms": 1200
  }
}
EVENTextraction_complete

Fired after the post-call LLM extraction finishes. The `fields` shape mirrors whatever extraction schema you configured on the agent.

Payload

JSON
{
  "event": "extraction_complete",
  "agent_id": "agt_2k9f1a",
  "workspace_id": "ws_4p2j1k",
  "timestamp": "2026-05-15T17: 56: 45Z",
  "data": {
    "call_id": "call_z8h2k1",
    "fields": {
      "summary": "Caller asked about pricing and booked a demo.",
      "success": true,
      "sentiment": "positive"
    }
  }
}
EVENTtransfer_initiated

Fired when the agent attempts a live transfer (warm or cold). The destination number is included so you can log routing.

Payload

JSON
{
  "event": "transfer_initiated",
  "agent_id": "agt_2k9f1a",
  "workspace_id": "ws_4p2j1k",
  "timestamp": "2026-05-15T17: 55: 42Z",
  "data": {
    "call_id": "call_z8h2k1",
    "to_number": "+15559876543",
    "reason": "caller_requested_human"
  }
}
EVENTcall_failed

Fired when an outbound dial cannot connect: no_answer, busy, voicemail (when AMD is on), or carrier error.

Payload

JSON
{
  "event": "call_failed",
  "agent_id": "agt_2k9f1a",
  "workspace_id": "ws_4p2j1k",
  "timestamp": "2026-05-15T17: 55: 15Z",
  "data": {
    "call_id": "call_z8h2k1",
    "error": "no_answer"
  }
}