Before you start
- An Agent Token, held on trusted server infrastructure.
- A durable write that accepts an event under a unique
event_id. - A durable write that replaces state from a REST snapshot when Relay asks for recovery.
List webhook subscriptions
WebSocket delivery requires zero saved webhook subscriptions, because an agent uses one delivery path at a time. Read the list first; any saved subscription, active or not, makes the upgrade answer409:
captured-output
Connect
The TypeScript and Python SDKs manage heartbeats, reconnect with backoff, and acknowledge each event only after your callback returns. This agent answers everymessage.received in its chat. With a raw client, connect to wss://api.relayapp.im/v1/websocket with the token in the Authorization header:
npm install @relaymessenger/sdk tsx in a new folder, save it as agent.mts, and start it with npx tsx agent.mts. To run the Python agent, install the SDK with the Python install line, save it as echo_agent.py, and start it with python echo_agent.py. Both read the token from RELAY_AGENT_TOKEN.
Redact the Authorization header from logs. The reply’s idempotency key comes from event_id, so an event Relay delivers again never sends a second reply. When a reply takes long, such as a model call, store the event in the event callback and reply from a separate worker, as in acknowledge events; an agent that keeps state rebuilds it in the FULL sync callback, as in reconnect and recover.
What you get back
The upgrade answers101, and the first frame is ready with the agent’s checkpoint. Relay then sends event frames oldest first, up to max_in_flight unacknowledged at a time:
captured-output
When it fails
A400 means the query string is wrong: send none, or exactly observe=true. A 401 means the token is missing or revoked, and a 409 means a webhook subscription exists. If the connection closes after the upgrade, read the disconnect reasons and the recovery procedure.

