Agent skill
Liveblocks Webhooks Skill
Receive and verify Liveblocks webhooks, which follow the Standard Webhooks specification. Use when setting up a Liveblocks webhook endpoint, getting the whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature verification, or handling events like userEntered, storageUpdated, ydocUpdated, commentCreated, threadCreated and notification.
Install this skill
npx skills add hookdeck/webhook-skills --skill liveblocks-webhooks
Liveblocks is realtime collaboration infrastructure: rooms, Storage, Yjs, Comments and notifications. It POSTs a signed JSON event to each endpoint you add in the project's Webhooks dashboard, for all events or a filtered subset. Signatures follow Standard Webhooks, so each delivery carries webhook-id, webhook-timestamp and webhook-signature headers.
When to Use This Skill
- How do I receive Liveblocks webhooks?
- How do I verify a Liveblocks webhook signature (
webhook-signature)? - Where do I find my Liveblocks webhook secret (
whsec_...)? - How do I handle
userEntered,storageUpdated,ydocUpdated,commentCreatedorthreadCreatedevents? - How do I send emails from Liveblocks
notificationevents? - Why is my Liveblocks webhook verification failing, or failing only on retries and replays?
Verification (core)
| Header | Value |
|---|---|
webhook-id | Identifies the message; retries reuse it. Your idempotency key (the body has no event id) |
webhook-timestamp | Unix seconds |
webhook-signature | One or more space-separated v1,<base64> entries |
Signed content is {webhook-id}.{webhook-timestamp}.{raw body}, HMAC-SHA256, base64 output. The key is the base64-decoded part of the whsec_... secret after the prefix. Compare against every v1, entry; ignore other versions. If webhook-id is absent, read svix-id, svix-timestamp and svix-signature instead, as Hookdeck does. The examples use the standardwebhooks library (npm and PyPI):
const { Webhook } = require('standardwebhooks');
// rawBody: exact request body (Buffer or string), NOT re-serialised JSON
function verifyLiveblocks(rawBody, headers, secret = process.env.LIVEBLOCKS_WEBHOOK_SECRET) {
const p = headers['webhook-id'] ? 'webhook' : 'svix'; // fall back to the svix-* names
new Webhook(secret).verify(rawBody, {
'webhook-id': headers[`${p}-id`],
'webhook-timestamp': headers[`${p}-timestamp`],
'webhook-signature': headers[`${p}-signature`],
}, { jsonParse: false }); // throws on a bad signature or stale timestamp → respond 401
}
The library always enforces a fixed 5-minute timestamp window. Hookdeck retries and replays resend the original webhook-timestamp, so the library rejects them once they are more than 5 minutes old. If you receive through Hookdeck and need late retries to pass, use the manual verifier in references/verification.md, which can drop or widen the window. In Express, read the body with express.raw({ type: '*/*' }) so any content type verifies, and parse JSON only after the check.
Liveblocks' own @liveblocks/node package offers new WebhookHandler(secret).verifyRequest({ headers, rawBody }), which verifies and returns a typed event. See references/verification.md for when to pick it.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Type and Payload
Every body is the same { type, data } envelope. The event name is the camelCase top-level type, and the event fields sit under data:
{
"type": "commentCreated",
"data": {
"projectId": "my-project-id",
"roomId": "my-room-id",
"threadId": "my-thread-id",
"commentId": "my-comment-id",
"createdAt": "2021-10-06T01:45:56.558Z",
"createdBy": "my-user-id"
}
}
The notification event is refined by data.kind: thread, textMention, or a custom $-prefixed kind you trigger from your server.
Common Event Types
| Event | Fired when |
|---|---|
userEntered / userLeft | A user connects to or disconnects from a room (numActiveUsers is the count after the change; not throttled) |
storageUpdated | A user writes to Storage (throttled at 60 s, so not sent for every write) |
ydocUpdated | A user changes a Yjs document in a room (throttled at 60 s) |
roomCreated / roomDeleted | A room is created (by API or by a user connecting to a missing room) or deleted |
commentCreated / commentEdited / commentDeleted | A comment changes |
commentReactionAdded / commentReactionRemoved | An emoji reaction is added to or removed from a comment |
commentMetadataUpdated | A comment's metadata is updated |
threadCreated / threadDeleted | A thread is created, or deleted (all comments removed or deleted manually) |
threadMetadataUpdated | A thread's metadata is updated |
threadMarkedAsResolved / threadMarkedAsUnresolved | A thread's resolved state changes |
notification | A user was mentioned or replied to and hasn't seen it; sent 30 minutes after the activity by default (configurable in project settings), for email notifications |
These 18 are the events the examples route. The docs list them with a note that more will follow (maxConnectionsReached is mentioned), so ignore types you don't recognise. See references/overview.md.
Delivery Semantics
Return a 2xx to acknowledge. Anything else is retried, 8 attempts in all (immediately, then after 5 s, 5 min, 30 min, 2 h, 5 h, 10 h and 10 h). If every delivery to an endpoint fails for 5 consecutive days, Liveblocks disables it; re-enable it from the dashboard. After long downtime (over 8 hours), replay messages one at a time or use Recover failed messages.... Deduplicate on webhook-id. The docs give no timeout or ordering guarantee, so respond quickly, process asynchronously, and use the timestamps in data if order matters. Return 400 for missing headers and 401 for a bad signature. Liveblocks no longer sends from a fixed IP list: verify the signature instead of allowlisting.
Environment Variables
LIVEBLOCKS_WEBHOOK_SECRET=whsec_xxxxx # full value from the endpoint's secret in the webhooks dashboard; handlers fail closed (500) without it
Local Development
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 liveblocks --path /webhooks/liveblocks
Add the printed URL as an endpoint in the Webhooks dashboard, then choose Test endpoint... and Send example to deliver a signed event.
Reference Materials
- references/overview.md - Event catalog, envelope and payload fields
- references/setup.md - Add an endpoint, find the secret, filter and test
- references/verification.md - Library and manual verification, gotchas, debugging