Skip to main content
Call responding when an inbound message starts a normal response:
Relay validates the target, commits Read, then sends chat.typing_indicator.started. A failed validation or receipt write sends no typing signal. Retries are safe. Read is idempotent. Typing refreshes its expiry. Groups use the same correlation as direct chats: message_id is the id of the inbound message from the message.received event you are responding to.

Four separate facts

Read implies Delivered. Delivery never starts typing. Typing alone never marks a message Read.

Delivered and Read answer different questions

The two rungs report two different things about your agent, and they are owned by two different parties. Delivered is not something you write code for. Relay takes it from the transport itself, so it arrives the moment your endpoint accepts the event and it cannot be faked, delayed, or switched off. An agent that is reachable is Delivered. An agent that is down is not. Read is the rung you control, and it means the agent has taken the message up. Nothing about either rung says the reply is finished.
Delivered is stamped from your webhook response, so anything your handler does before answering is time the sender spends watching a message with no receipt under it. A handler that runs a model call before its 2xx turns a slow turn into a slow Delivered. Answer first, then work. See Webhooks.
Delivered + typing is valid during proactive activity. Ordinary response typing must identify the consumed message through /responding.

Consume without replying

Use /read when your backend consumes a message without starting a response:
/read, /delivered, and /responding all answer with the watermark after the call:
/responding spends from the read-receipt budget and the typing budget in the same call. Watermarks make that cheap to live with: mark the newest message you consumed rather than every message behind it.

Record delivery

Relay records Delivered when a subscribed webhook accepts a message. Webhook consumers get Delivered from Relay and need no extra call. For a webhook, the receipt is stamped when your response returns, so the speed of your handler is the speed of the sender’s Delivered. Acknowledge as soon as the event is verified and safely in hand, and do the work after:
1

Verify the signature

Reject anything that fails, with 401.
2

Skip a replay

Delivery is at least once. An event_id you already accepted is answered 200 and dropped.
3

Hand the event somewhere durable

A queue, a job table, or a background runtime. This step is what makes the next one honest.
4

Answer 2xx

The sender sees Delivered from here.
5

Do the work

Call /responding, run your model, send the reply.
A 2xx ends Relay’s delivery, so it will not send the event again. Retries move from Relay to you, which is why step 3 comes before step 4. Derive your reply’s idempotency key from the inbound event_id so your own retry replays the same send instead of posting twice. Call /delivered only when you explicitly need to stamp delivery at a point you control, such as after you durably enqueue the message:
Send it on ingest, before /read or /responding. Recording Read advances the delivered watermark as well, so a /delivered call that arrives after a Read for the same message answers advanced: false and emits no delivery event.
Order matters more than frequency. One /delivered on ingest gives the sender Sent, Delivered, then Read. The same call after /responding changes nothing the sender can see.

Track your replies

Relay emits two events as your reply moves through the conversation. Both carry through_sequence. Conversation history projects each outbound message as sent, delivered, or read.

Next steps