Receiver reference
Suzo defines a versioned webhook contract for all runtimes. It is a Suzo integration contract, not a claim that every MCP client already implements a wake endpoint. The receiving adapter decides how to start or resume the exact paired agent session.
The Add agent Copy setup prompt flow connects MCP first, then asks the receiving agent to assess and offer incoming calling and local speech before configuring either; the complete guide is served at https://api.suzo.app/setup/agent and maintained in agent-setup.md. Agents register and probe only their own receiver with their own scoped credential. Manual setup is also available under Agent → Calling the agent → Manual configuration. Enter the receiver URL and bearer token, then Save & check. The profile's Call button starts an outgoing call when configured, or opens setup when needed.
The phone reserves a call and shows Calling while Suzo wakes the paired session. The receiver still uses the same versioned event and request UUID. When the agent invokes call_user with that UUID, Suzo joins the reserved outgoing call without an incoming push or another ring. The phone then connects media automatically. A receiver acknowledgment means queued, not connected. The user can cancel while waiting; a late agent response returns that ended call and never rings them. Calls time out after 90 seconds awaiting the agent, or 60 seconds preparing media. Voice/provider settings are frozen when the user taps Call. Disabling Allow calls from agent blocks unsolicited incoming calls, not calls the user initiates.
Runtime wake-up compatibility must be verified against the actual bound session. Local HTTP/MCP tests establish the protocol and lifecycle, not physical-device audio or a particular runtime's wake behavior.
Receiver protocol (version 3)
Suzo sends an authenticated HTTP POST to the configured URL. HTTPS is required except for a receiver on localhost. The URL is reached by the Suzo server, so a self-hosted receiver can use Tailscale HTTPS if that server can reach its tailnet address. The phone does not connect directly to the runtime.
Authorization: Bearer <receiver token>
Content-Type: application/json
Connection probe:
{
"version": 3,
"type": "suzo.hook.check",
"request_id": "a unique UUID",
"agent_id": "the paired Suzo agent UUID",
"requested_at": "an ISO-8601 timestamp"
}
A call request has the same fields with type: "suzo.call.request". Acknowledge with HTTP 200 or 202 and:
{"version":3,"accepted":true,"request_id":"the same request UUID"}
A check MUST NOT wake a session. A call request must durably enqueue work once per request ID, acknowledge promptly, then wake the bound session. The receiver should:
- Validate the bearer token and bound
agent_id. - Deduplicate call requests, including retries after a response timeout.
- Use the runtime's authorized session API or local bridge to resume the intended paired session. Preserve its permissions, approval policy, task context and workspace. Queue if that session is busy; do not silently substitute another agent or thread.
- Instruct that agent to answer through Suzo MCP
call_user, using the exact receivedrequest_idto join the existing outgoing call. If it is already ended, do not create another call. Then processnext_call_event,reply_to_call,end_callandacknowledge_callas usual.
The receiver already has its scoped MCP credentials. Suzo sends no agent token, provider key, transcript or shell command in this webhook. Receiver credentials stay in the private server store; device reads return only the URL, a credential-presence flag and last successful check. Tokens are never returned in the agents list. Redirects are rejected. Editing the URL requires entering a token for that endpoint again.
A receiver must distinguish a scheduled callback from actual runtime admission. Some plugin APIs return success before the session authority accepts the work. Verify the durable admission/receipt through the runtime's supported facilities before marking the wake delivered; do not blindly retry an uncertain asynchronous dispatch. In particular, a Hermes plugin's inject_message return value alone does not prove its target session accepted the turn. Keep the human's original session binding and permissions; do not bypass runtime refusals or patch the harness.
With its existing agent credential, the receiver can read GET /v1/agent/calling/requests/:requestId to check whether the outgoing call is still waiting (dialing). Stop stale work after cancellation or expiry. If its runtime refuses the wake, POST /v1/agent/calling/requests/:requestId/failure with {"reason":"session_rejected"}. Supported reasons are session_unavailable, session_rejected and wake_unconfirmed; these map to concise app messages, never raw runtime logs. The report ends only a still-waiting outgoing call, is idempotent, and cannot disconnect an agent that already joined. Both routes are scoped to the receiver's own agent and request UUID. Never use a phone credential for this.
A successful probe means the receiver understands the contract, not that an agent woke. An accepted call request means queued, not that a call connected. The iPhone stays on the outgoing screen until the agent joins, the user cancels, or the call times out. An existing version-1 receiver needs no configuration change for this flow: the matching request ID attaches to the reserved call.
Existing version-2 receivers continue receiving suzu.hook.check and suzu.call.request with version-2 acknowledgments. They are never silently upgraded. New receivers register protocolVersion: 3 when saving their URL and token. Existing receivers without a saved protocol version keep version 1. Unversioned older registration clients also keep version 1; explicit version 3 registration is required to use the Suzo event names. The acknowledgment must match the configured version. Changing versions clears the previous check and requires checking again. No call request is retried with another protocol automatically.
Runtime adapters
Codex: use the documented App Server session protocol: initialize an authorized local client, resume the saved thread and start a turn with the callback instruction. The bridge must bind an explicit thread ID and handle busy-session state and approval requests. Do not assume another app-server process automatically controls an already-running desktop chat. A connection to that chat's owning process or a user-configured bridge is still necessary.
Hermes: its documented incoming webhooks and event hooks are different mechanisms. A stock webhook can create a separate session; mirroring its output to a Telegram session is not resuming that session. Bind the Suzo receiver to the installed runtime's supported session start/resume mechanism. Validate its API before writing an adapter; no Hermes-specific wake endpoint has been assumed or tested here.
Another runtime: keep the same Suzo POST and acknowledgment contract. A process hook that reports lifecycle events outward is not automatically an inbound wake server. Use the runtime's authorized session API and verify the binding to the intended session.
The app can copy receiver instructions containing its paired agent ID. The runtime-specific guide changes the wake implementation; the request contract stays the same.
Server endpoints
Phone management endpoints require the paired phone credential:
GET /v1/agents/:id/calling: redacted configuration.PUT /v1/agents/:id/calling: saveurl,tokenandprotocolVersion: 3for a new receiver.POST /v1/agents/:id/calling/check: side-effect-free receiver check.POST /v1/agents/:id/calling/dial: reserve an outgoing call and dispatch a wake withrequest_id(UUID). Returns HTTP 202 with the call, initiallydialing. Reusing the ID returns that call; it never starts another.POST /v1/agents/:id/calling/request: legacy callback-only endpoint for older clients; it reserves no outgoing session and still rings the phone when the agent responds.DELETE /v1/agents/:id/calling: remove the saved receiver.
Agent self-service endpoints require that agent's own bearer token and never accept another agent's ID:
GET /v1/agent/profile: redacted own identity.PATCH /v1/agent/profilewith canonicalruntime(and optionalruntimeNameforother): report/correct own harness; preserves the user's contact name.GET/PUT /v1/agent/setup: read/report own optional-feature compatibility assessment; does not configure a receiver, start speech or place a call.GET /v1/agent/calling: redacted own receiver status.PUT /v1/agent/callingwithurl,tokenandprotocolVersion: 3: register own receiver.POST /v1/agent/calling/check: side-effect-free protocol probe.DELETE /v1/agent/calling: remove own receiver.
An agent credential cannot send a phone-initiated callback request, list other contacts or configure their receivers. Existing clients may keep their current MCP credential and report missing harness metadata through the profile route without pairing again.