Webhooks Reference
Relay sends webhooks from Relay to your agent backend. Webhooks are retryable, signed, and independent from the agent reply API.
Headers
Section titled “Headers”Relay-Event: message.receivedRelay-Webhook-Id: whk_01JZ8G9H2K4M6N8P0Q1R3S5T7VRelay-Timestamp: 2026-07-06T17:24:00ZRelay-Signature: v1=5b5c4f...Content-Type: application/jsonRelay signs timestamp + "." + raw_body with HMAC-SHA256 using your webhook_secret.
message.received
Section titled “message.received”Sent when a Relay user sends a message to your agent contact.
{ "id": "evt_01JZ8F2W6R4S7E2D3Q9M0N1P2A", "type": "message.received", "created_at": "2026-07-06T17:24:00Z", "agent": { "id": "agt_01JZ8A1Relay", "handle": "doctor" }, "conversation": { "id": "cnv_01JZ8D9H3J7K4M2N1P6Q8R0S2T", "kind": "direct", "relay_user_id": "usr_01JZ8C0X2B5N7M9Q1R3S4T6V8W", "thread_id": null, "created_at": "2026-07-06T17:20:00Z" }, "message": { "id": "msg_01JZ8F2W7A8B9C0D1E2F3G4H5J", "sender": { "kind": "user", "id": "usr_01JZ8C0X2B5N7M9Q1R3S4T6V8W", "display_name": "Advait" }, "created_at": "2026-07-06T17:24:00Z", "reply_to_message_id": null, "content": [ { "id": "blk_01", "type": "text", "text": "Can you look at this PDF?" } ], "fallback_text": "Can you look at this PDF?" }, "capabilities": { "content_blocks": ["text", "image", "audio", "video", "file", "link_preview", "card", "buttons"], "typing": true, "receipts": true, "streaming": ["http_stream", "message_delta_events"] }}call.started
Section titled “call.started”Sent when a Relay user starts a voice or video call with your agent. Your call worker joins the LiveKit room using the provided token.
{ "id": "evt_01JZ8K2M4P6Q8R0S1T3V5W7X9Y", "type": "call.started", "created_at": "2026-07-06T17:31:00Z", "agent": { "id": "agt_01JZ8A1Relay", "handle": "doctor" }, "conversation": { "id": "cnv_01JZ8D9H3J7K4M2N1P6Q8R0S2T", "kind": "direct", "relay_user_id": "usr_01JZ8C0X2B5N7M9Q1R3S4T6V8W" }, "call": { "id": "call_01JZ8K2N6R8S0T1V3W5X7Y9Z0A", "kind": "voice", "room_name": "relay-call-call_01JZ8K2N6R8S0T1V3W5X7Y9Z0A", "agent_token": "livekit_agent_join_token", "started_at": "2026-07-06T17:31:00Z" }}Verify signatures
Section titled “Verify signatures”import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyRelayWebhook( rawBody: Buffer, timestamp: string, signatureHeader: string, webhookSecret: string,) { const expected = createHmac("sha256", webhookSecret) .update(`${timestamp}.${rawBody}`) .digest("hex"); const received = signatureHeader.replace(/^v1=/, "");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(received, "hex"));}import hashlibimport hmac
def verify_relay_webhook(raw_body: bytes, timestamp: str, signature_header: str, webhook_secret: str) -> bool: signed = timestamp.encode("utf-8") + b"." + raw_body expected = hmac.new(webhook_secret.encode("utf-8"), signed, hashlib.sha256).hexdigest() received = signature_header.removeprefix("v1=") return hmac.compare_digest(expected, received)Delivery rules
Section titled “Delivery rules”- Return
2xxafter the event is accepted. - Do agent work asynchronously when the turn may take more than a few seconds.
- Use
event.idas your webhook idempotency key. - Use
conversation.idas the Relay session identity. - Use the REST API to send replies, typing indicators, receipts, and reactions.