Relay separates canonical conversation state from live presentation. Knowing which is which tells you what you can rely on after a failure.

Incoming messages

When a user sends a message, Relay commits it to the conversation before adding message.received to the agent’s durable event log. An agent consumes that log through exactly one transport.
The two transports are mutually exclusive. Polling while a webhook is enabled returns 409 conflict.

Signed webhooks

1

Relay writes the event

The event and one outbox row per matching active endpoint are written in the same transaction.
2

Relay signs and POSTs it

The exact JSON body is signed and sent to your registered HTTPS URL.
3

Your backend verifies and deduplicates

Verify the signature before parsing, then deduplicate on event_id.
4

Your backend accepts durably

Enqueue the event and return 2xx quickly.
Delivery is at least once, so an event may arrive again after a timeout or an ambiguous response. A successful message.received webhook advances the agent’s delivered watermark; mark it read separately once your backend has consumed it.

Long polling

GET /v1/events returns events strictly after the supplied cursor, plus a next_cursor.
Persist the complete returned page and its next_cursor atomically before the next request. Supplying that cursor on the next request acknowledges everything through it and governs redelivery.
The sender-visible delivered receipt advances as soon as Relay hands the page to the consumer. The durable acknowledgement remains the redelivery watermark.
Cursors are scoped to the agent, not the token. Rotating an Agent Token never resets the ledger.

Long-poll errors

Outgoing messages

POST /v1/messages commits the message and returns 202 Accepted. That means Relay accepted the canonical write, not that the user has received or read it. Every send requires an Idempotency-Key. Derive the key from the incoming event_id. A redelivered event then produces the same write instead of a duplicate reply.

Delivery and read state

Receipts are monotonic watermarks through a conversation sequence:
Relay emits message.delivered and message.read only when the corresponding watermark advances. Read implies delivered. Conversation history projects those watermarks onto each message.

Live state

Typing indicators are pushed to active devices and never enter the durable event log. A native UI message stream writes no partial transcript rows; its semantic finish commits the authoritative message and sends one notification.
If generation aborts, errors, or disconnects before completion, Relay commits nothing. Retry the complete stream with the same idempotency key.

Recovery rule

Events tell your backend what changed. Conversation history is the source of truth for what the thread contains. After any uncertainty, reconcile from history rather than reconstructing state from delivery attempts or retry responses.

Next steps