Cập nhật lần cuối
Webhooks
An alert rule can deliver to an HTTPS endpoint next to in-app, email and Telegram. Every delivery is a JSON POST signed with the channel’s secret, which is shown once when the channel is created.
Setting up an endpoint
- In the dashboard open Alerts → Rules → New rule, pick the event types and add a Webhook channel with your HTTPS URL. With an API key, send the same rule to
POST /v1/alerts/rules(scopewebhook:manage). - Copy the signing secret (
whsec_…). It is returned once, inwebhookSecrets[].signingSecret, one per webhook channel. Store it with your endpoint. - Answer with a
2xxwithin 10 seconds and do the work asynchronously. Redirects are not followed, and private, loopback or link-local addresses are refused. POST /v1/alerts/rules/{id}/testsends a sampleenvelopedelivery (sourceEventIdstarts withtest_) so you can check your handler.
Events
A rule lists the event types it reacts to in eventTypes; an empty list matches every type. These are the types worth alerting on:
| Type | Fires when |
|---|---|
envelope | A treasure box was observed in a watched room. The rule’s minimum diamond value applies. |
goodyBag | A goody bag was observed in a watched room. The rule’s minimum diamond value applies. |
gift | A gift was sent in a watched room. Fires on every gift: the minimum diamond value does not apply. |
minDiamondCount defaults to 49 and, together with a floor of 1.51 diamonds per viewer, filters treasure boxes and goody bags only. LIVE start and end events are not delivered yet.
Headers
| Header | Value |
|---|---|
content-type | application/json |
x-roomscope-alert-id | <ruleId>:<eventId>, the same value as alertId in the body. Deduplicate on it. |
x-roomscope-timestamp | Unix time in seconds when this attempt was signed. |
x-roomscope-schema-version | Payload schema version, currently 2026-11-03 (adds `links`). |
x-roomscope-signature | sha256=<hex HMAC-SHA256 of "<timestamp>.<raw body>">. |
Payload
One event per request, as compact JSON. diamondCount, peopleCount and unpackAt (Unix seconds) are set for envelope and goodyBag and are null for other types.
{ "schemaVersion": "2026-11-03", "alertId": "8f0c…:evt_…", "rule": { "id": "8f0c…", "name": "Big treasure boxes" }, "event": { "type": "envelope", "sourceEventId": "evt_…", "roomId": "7432…", "creatorUniqueId": "room.one", "region": "VN", "diamondCount": 5000, "peopleCount": 120, "score": 5000, "unpackAt": 1793520000, "observedAt": "2026-11-01T10:02:31.123Z" }, "links": { "creator": "https://app.roomscope.io/creators/room.one", "session": "https://app.roomscope.io/live?creator=room.one" }, "occurredAt": "2026-11-01T10:02:31.123Z", "deliveredAt": "2026-11-01T10:02:33.456Z"}Verifying signatures
The signature is HMAC-SHA256, keyed with the full whsec_… secret, over the timestamp header, a dot and the raw body: <timestamp>.<body>. The hex digest is sent as sha256=<hex> in x-roomscope-signature. Compare in constant time, and reject timestamps outside a window you choose (five minutes below) to stop replays.
import { createHmac, timingSafeEqual } from "node:crypto"; // rawBody: the exact bytes received, e.g. express.raw({ type: "application/json" }).// secret: the full whsec_… value shown when the webhook channel was created.export function verify(rawBody, headers, secret, toleranceSec = 300) { const signature = headers["x-roomscope-signature"]; const timestamp = headers["x-roomscope-timestamp"]; if (!signature || !/^\d+$/.test(timestamp ?? "")) return false; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSec) return false; const expected = "sha256=" + createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex"); const a = Buffer.from(signature, "utf8"); const b = Buffer.from(expected, "utf8"); return a.length === b.length && timingSafeEqual(a, b);}Retries and failures
- A status below 300 is a success. A
5xx,408,429, network error or timeout is retried; any other4xxor a3xxis a permanent failure. - A delivery gets at most 4 attempts, sent one after another without a delay. Each attempt is signed again, so
deliveredAt, the timestamp and the signature change. Deduplicate onalertId, not on the body. - After a successful delivery the same
alertIdis not delivered again for 7 days. - Each delivered webhook costs 1 credit. A workspace can receive up to 100,000 webhooks per calendar month (UTC); deliveries above that are dropped.
- Failed deliveries are not listed in the dashboard yet. Log the requests you receive if you need an audit trail.
Telegram and email
The same rule can also deliver to a connected Telegram chat or to an email address; those channels need no signature handling. Connect Telegram from Alerts → Channels.