Skip to content

Webhooks

c0dewhacker edited this page May 7, 2026 · 1 revision

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.


Endpoint management

Creating an endpoint

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.

Editing and disabling

Use Admin → Webhooks → Edit to change the URL, event subscriptions, secret, or enabled state. Disabled endpoints receive no deliveries but retain their delivery history.

Ping

Use the Ping button to send a test booking.created delivery to the endpoint. This lets you verify connectivity before subscribing to live events.


Events list

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

Payload structure

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.

Example — booking.created

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

Enrichment by event domain

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)

HMAC signature verification

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

Verifying the signature

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.


Delivery log and retries

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.


API endpoints

See REST-API#webhooks--apiv1webhooks for the full endpoint reference.

Clone this wiki locally