Skip to main content
This page is for the team building the connector — the Hermes, OpenClaw, or custom runtime that receives escalated Flowyte conversations and talks back and forth with the customer. It’s the technical companion to AI Harnesses, which covers the operator-side setup. The flow: Flowyte opens a durable escalation session, notifies you with a signed webhook, and you claim the session and drive it over REST — read the context, post replies (which reach the customer in their original thread), and finally resolve or return control to the AI. Flowyte owns the conversation record; your connector is a participant, never the source of truth.

1. Authenticate with a scoped key

Every REST call uses a scoped secret key (flowyte_sk_…), minted by the operator for the destination and shown once:
The key’s scopes gate exactly what the connector may do — request only what you need. These scopes are separate from escalation_destinations:*: a connector key works conversations but can’t edit destinations. Keys are organization- and destination-scoped: a key only ever sees its own tenant’s sessions. Cross-tenant calls fail closed as 404.
Never expose local file paths, prompts, model names, or secrets in a reply, a resolve note, or a shipped log — the customer sees message text verbatim.

2. Verify the signature first

Flowyte delivers webhooks to your endpoint_url over its signed pipeline. The body is a stable envelope — route on the channel field in every notification:

Signature headers

Verification recipe

1

Check the timestamp

Reject if |now − Flowyte-Timestamp| > 300s (replay window).
2

Recompute the signature

Compute hex(HMAC_SHA256(secret, Flowyte-Delivery + "." + Flowyte-Timestamp + "." + rawBody)) using the raw request bytes — re-serializing the JSON changes whitespace and breaks the match.
3

Compare in constant time

Accept only if it equals Flowyte-Signature, using a constant-time compare (never ==). During a rotation, also accept if any secret you hold verifies Flowyte-Signature-Prev.
A bad signature or a stale timestamp is a 401 — do no work.
Webhooks are the doorbell, not the mailbox — they tell you something happened; the REST API is the source of truth. Delivery is at-least-once, so a webhook may be redelivered — dedupe on Flowyte-Delivery. Don’t parse conversation content out of webhooks; read it from /messages.

3. Claim, reply, resolve

Claim

POST /escalations/{id}/claim takes ownership and starts an activity-based lease (chat ≈ 2 min, SMS ≈ 4 h). Claiming is idempotent for your key — re-claiming a session you hold just re-extends the lease — and guarded, so a second connector gets 409 claim_conflict.
Claim with retry on 409 / 404. A redelivered webhook or a race can transiently 409, and the session may not be visible for a beat (404) right after it’s created — back off briefly and retry a few times.
The claim response (and GET /escalations/{id} as the owner) returns the context package: the last N transcript turns (each with its sequence number), the escalation reason, any verified identifiers (SMS: the caller’s phone), and completed or failed tool actions (names and status only). It’s returned only to the claiming owner and is rebuilt at claim time, so you start current with any turns that arrived while the notification was in flight. Claim can also fail with 409 destination_unavailable (the harness was disconnected or the destination is inactive) or 403 plan_required (the organization is below Starter — the handshake test is exempt).

Read and sync

Every turn — customer and your own replies — carries the conversation’s sequence number (seq), the single ordering and dedup key. To catch up, or after a reconnect, poll GET /escalations/{id}/messages?after_seq=N and process strictly increasing seq values. Never assume a webhook told you everything; the /messages mailbox is authoritative and ordered.

Reply

  • client_message_id is your idempotency key. A replay with the same id returns the original receipt ({ messageId, seq, delivered }) instead of double-sending. Use one stable id per logical message and reuse it across retries.
  • The reply is delivered to the customer in their original thread — chat shows a “specialist has joined” divider; SMS sends a text.
  • Media (mediaUrls) is chat-only; on SMS it returns 422 media_unsupported_channel.
  • SMS compliance: a reply to an opted-out recipient returns 409 sms_suppressed; a quiet-hours send returns 200 with delivered: false. These are terminal — don’t retry.
Any owner action (a message, typing, heartbeat, even a /messages read) extends your lease. Send POST /escalations/{id}/heartbeat if you go quiet but want to keep the thread; POST /escalations/{id}/typing shows a typing indicator (chat only). If the lease expires, the thread returns to the AI and you get 409 lease_expired — re-claim to continue.

Finish

4. Channels: chat vs SMS

The lifecycle is identical across channels — route on the channel field — but:
  • Chat replies appear live in the website widget; typing indicators and media attachments are supported.
  • SMS threads are customer-initiated only. You reply inside an SMS conversation the customer already started — you cannot open one. (Outbound initiation is coming soon, tied to Flowyte’s outbound campaign rules.) SMS has no typing indicator and no media in v1, and it requires the operator’s completed A2P 10DLC (TCR) registration — the same registration their inbound number already needs. See SMS.
Harness conversations require the operator’s organization to be on Starter or higher. The handshake test works on any plan, so setup can precede the upgrade.

5. The handshake test

Before a destination can route real conversations, the operator runs a handshake test (POST /escalation-destinations/{id}/test) that proves your connector can talk back and forth:
  1. Flowyte opens a sandbox escalation on a synthetic conversation and sends you a signed escalation.test webhook — a normal escalation.requested envelope plus "test": true.
  2. Your connector must, within the window: verify the signature → claim → post one message → resolve.
  3. Flowyte reports a step ledger ({ webhook_delivered, claimed, message_posted, resolved }) with an actionable hint on failure. Only a green run flips the destination to active.
Treat escalation.test exactly like escalation.requested — same code path. Test messages reach no customer and are never billed, but your connector doesn’t need to know that; just claim, reply, resolve.

Hard rules

  • Verify Flowyte-Signature (constant-time; 300s replay window; accept Flowyte-Signature-Prev during rotation) before doing any work.
  • Claim with retry on 409 / 404; claim is idempotent for your key.
  • Idempotent client_message_id on every reply; a replay returns the original receipt.
  • Dedupe by seq; gap-sync via GET /escalations/{id}/messages?after_seq=N. Webhooks are the doorbell; the REST API is the mailbox.
  • Route on the channel field in every notification.
  • Sessions are tenant- and destination-scoped — you only ever see your own.
  • Never expose local paths, prompts, model internals, or secrets in replies, notes, or logs.
  • SMS is customer-initiated only and requires completed TCR registration; harness conversations require Starter or higher.
Back to AI Harnesses for the operator setup, or drive the same surface from an agent over the MCP gateway.