Skip to content

Webhooks Reference

Relay sends webhooks from Relay to your agent backend. Webhooks are retryable, signed, and independent from the agent reply API.

Relay-Event: message.received
Relay-Webhook-Id: whk_01JZ8G9H2K4M6N8P0Q1R3S5T7V
Relay-Timestamp: 2026-07-06T17:24:00Z
Relay-Signature: v1=5b5c4f...
Content-Type: application/json

Relay signs timestamp + "." + raw_body with HMAC-SHA256 using your webhook_secret.

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"]
}
}

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"
}
}
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"));
}
  • Return 2xx after the event is accepted.
  • Do agent work asynchronously when the turn may take more than a few seconds.
  • Use event.id as your webhook idempotency key.
  • Use conversation.id as the Relay session identity.
  • Use the REST API to send replies, typing indicators, receipts, and reactions.