# Webhooks Receive signed room and participant events on your server. ## Provide a receiver URL Use a server-side client configured with a secret key that has webhooks:write. See [RelayServer](/reference/sdk/server/node/RelayServer). ```ts import { RelayServer } from '@relayrtc/node' const relay = new RelayServer({ apiUrl: process.env.RELAYRTC_API_URL!, apiKey: process.env.RELAYRTC_SECRET_KEY!, }) ``` The webhook URL is an HTTP endpoint in your own backend. RelayRTC sends events to that endpoint. It is not the browser signaling URL or a URL generated by the SDK. Create and configure the endpoint through the console, [REST](/reference/api/v1/webhooks/create) or the [Node SDK](/reference/sdk/server/node/webhooks/create). ```ts const endpoint = await relay.webhooks.create({ url: 'https://your-app.example/webhooks/relayrtc', eventTypes: ['room.ended', 'participant.joined'], }) ``` Save `endpoint.signingSecret` securely. It is returned on creation and rotation, not on ordinary reads. ## Verify before parsing This example uses Express. Install it alongside the Node SDK: ```bash npm install express pnpm add express yarn add express ``` ```ts import express from 'express' import { verifyWebhookRequest } from '@relayrtc/node' const app = express() app.post('/webhooks/relayrtc', express.raw({ type: 'application/json' }), (req, res) => { const valid = verifyWebhookRequest( process.env.RELAYRTC_WEBHOOK_SECRET!, req.body, req.headers, ) if (!valid) return res.sendStatus(401) const event = JSON.parse(req.body.toString('utf8')) console.log(event.id, event.type) return res.sendStatus(204) }) app.use(express.json()) ``` > **Process each event once** > This receiver shows verification only. Before applying side effects, atomically record the event ID with your processing result. Retries and explicit replay can deliver an event more than once. ## Delivery policy Each run allows 8 attempts over at most 7 days. Network errors, HTTP 408, 425, 429 and 5xx responses are retried. Other unsuccessful HTTP responses stop the run. Backoff delays are 5 seconds, 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour and 6 hours, with up to 20% additional jitter. Completed delivery logs are retained for 30 days after the latest outcome. Inspect [delivery logs](/reference/sdk/server/node/webhooks/deliveries/get) before retrying a failed receiver. ## Explicit replay Replay starts a new run while retaining event and delivery IDs. It requires an enabled endpoint, a terminal delivery and its current replay count. ```ts await relay.webhooks.deliveries.replay(endpoint.id, delivery.id, { expectedReplayCount: delivery.replayCount, }) ``` There are at most 100 explicit replays per delivery. ## Secret rotation Rotation immediately replaces the previous secret. Update your receiver promptly. Older signatures cannot be verified with the new secret. For the exact headers and signing format, see [verifyWebhookRequest](/reference/sdk/server/node/verifyWebhookRequest). ## Receiver address and replay destination Use a public HTTPS endpoint. Delivery rejects localhost, private network addresses and other destinations that could reach internal services. A local receiver needs a public tunnel. Explicit replay uses the endpoint’s current URL and signing secret; automatic retries retain the URL selected for that delivery run. See [event types and payloads](/reference/api/v1/schemas/webhook-event).