Repository navigation
Webhooks
Roomer can push real-time event notifications to any HTTPS endpoint you register. Each delivery is signed with HMAC-SHA256 so your receiver can verify the payload came from your Roomer instance.
Webhook endpoints are managed by SUPER_ADMINs at Admin → Webhooks in the UI, or via the REST API#webhooks--apiv1webhooks.
In the UI go to Admin → Webhooks → Add endpoint. Provide:
- URL — the HTTPS URL Roomer will POST events to
- Events — one or more event types to subscribe to (see the Events list below)
- Secret — a signing secret (min 16 characters); if omitted, a random 64-character hex secret is generated automatically
Endpoints are enabled by default. You can disable an endpoint at any time without deleting it.
Use Admin → Webhooks → Edit to change the URL, event subscriptions, secret, or enabled state. Disabled endpoints receive no deliveries but retain their delivery history.
Use the Ping button to send a test booking.created delivery to the endpoint. This lets you verify connectivity before subscribing to live events.
Roomer emits 18 event types across four domains:
| Domain | Event | Fired when |
|---|---|---|
| Booking | booking.created |
A booking is created |
booking.modified |
A booking's times or notes are changed | |
booking.cancelled |
A booking is cancelled | |
booking.completed |
A booking is auto-completed by the background job | |
| Queue | queue.joined |
A user joins the waitlist for an asset |
queue.promoted |
A queue entry is promoted (slot became available) | |
queue.claimed |
A promoted entry is claimed (booking created) | |
queue.expired |
A promoted entry expired before being claimed | |
queue.cancelled |
A user leaves the queue | |
| Asset | asset.created |
An asset is created |
asset.updated |
An asset's metadata is updated | |
asset.status_changed |
An asset's bookable/disabled state changes | |
asset_assignment.created |
An asset is permanently or temporarily assigned to a user | |
asset_assignment.returned |
A temporary (checked-out) assignment is returned | |
| User | user.created |
A user account is created |
user.updated |
A user's profile or role is updated | |
user.suspended |
A user account is suspended | |
user.imported |
A user is created via bulk CSV import |
Every delivery is an HTTP POST with Content-Type: application/json. The top-level shape is:
{
"event": "booking.created",
"timestamp": "2025-05-07T09:14:32.000Z",
"data": { ... }
}The data object is the raw record supplemented with enriched human-readable fields — so your receiver doesn't need to call back into the Roomer API to look up names.
{
"event": "booking.created",
"timestamp": "2025-05-07T09:14:32.000Z",
"data": {
"id": "bk_01HZ...",
"startDate": "2025-05-12",
"endDate": "2025-05-12",
"status": "CONFIRMED",
"user": {
"id": "usr_01HZ...",
"email": "alice@example.com",
"displayName": "Alice Smith"
},
"asset": {
"id": "ast_01HZ...",
"name": "Desk 14A",
"category": { "name": "Hot Desk" },
"primaryZone": { "name": "North Wing" },
"floor": {
"name": "Ground Floor",
"building": { "name": "Acme HQ" }
}
}
}
}| Event domain | Enriched fields added |
|---|---|
booking.* |
user (id, email, displayName), asset (id, name, category, zone, floor, building) |
queue.* |
user (id, email, displayName), asset (id, name, category, zone, floor, building) |
asset.* |
asset (name, category, zone, floor, building) |
asset_assignment.* |
asset (id, name, category, zone, floor, building), user (id, email, displayName) |
user.* |
user (id, email, displayName, globalRole, accountStatus, departmentId) |
Every delivery includes three HTTP headers:
| Header | Description |
|---|---|
X-Roomer-Event |
The event type, e.g. booking.created
|
X-Roomer-Delivery |
A unique UUID for this delivery attempt |
X-Roomer-Signature |
sha256=<hex> — HMAC-SHA256 of the raw request body signed with the endpoint secret |
Read the raw request body before parsing JSON, then compute the HMAC and compare:
import crypto from 'crypto'
function verifySignature(secret: string, rawBody: string, signatureHeader: string): boolean {
const expected = `sha256=${crypto.createHmac('sha256', secret).update(rawBody).digest('hex')}`
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}import hashlib, hmac
def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)Always use a timing-safe comparison to prevent timing attacks.
Every delivery attempt is recorded. In the UI go to Admin → Webhooks → (endpoint) → Deliveries to see the log, including HTTP status codes and error messages.
If a delivery fails (non-2xx response or network error) Roomer retries automatically with exponential back-off via the pg-boss job queue:
| Behaviour | Value |
|---|---|
| Max attempts | 5 |
| Initial retry delay | 60 seconds |
| Back-off | Exponential |
| Expiry | 24 hours after first attempt |
A delivery is marked successful only when your endpoint returns a 2xx HTTP status within the 10-second timeout.
See REST-API#webhooks--apiv1webhooks for the full endpoint reference.
Install & Run
- Getting Started
- Development Setup
- Production Deployment
- Kubernetes Deployment
- Backup and Recovery
- TLS Configuration
- Configuration Reference
Using Roomer
- Buildings and Floors
- Zones and Assets
- Users, Groups and Permissions
- Booking and Queue
- Bulk CSV Import
- Enterprise Auth
- Email Notifications
- Webhooks
- Reports and Leases
Developer