Webhook Notification Channel Help
Overview
Webhook is the 6th channel in Notification Workflows. Like LINE / Email / SMS / Voice / Speaker, it uses the same "anchor event + offset" mechanism, but instead of notifying a person it delivers reservation events to any external endpoint via an HTTPS POST. Use it to feed reservation events into your own systems or third-party services (inventory sync, analytics, custom alerting, etc.).
Open it from the sidebar via "🔔 Notification Workflows" → the Channels tab → the Webhook card, which links to /notifications/channels/webhook (endpoint management).
This feature is enabled per organization. If the Webhook card doesn't appear on the Channels tab, the
notification_webhook_channelfeature flag is OFF for your organization. Ask support to enable it.
Registering an endpoint here is only half the setup. To actually make Webhook fire, open a workflow in the Workflows tab of Notification Workflows, set its channel to "Webhook," and pick the endpoint to send to. This page manages endpoints and signing keys; the Workflows tab decides which event triggers a send.
Detailed features
Feature 1: Registering endpoints
- Enter the receiving URL in the URL field (https only)
- Optionally enter a description to help identify the endpoint
- Click "Add"
On success, the signing secret is shown once. It is not shown again after you leave the page, so copy it into your receiving system right away (you can retrieve it again later via "Reveal secret" — see Feature 2).
Each registered endpoint appears as a card with the following actions.
| Action | What it does |
|---|---|
| Edit | Change the URL or description |
| Enabled/disabled toggle | Disabling stops deliveries to this endpoint (the record itself is kept) |
| Reveal secret | Show the current signing secret again |
| Rotate secret | Issue a new signing secret (see Feature 2) |
Endpoints cannot be deleted. Switch an endpoint you no longer use to "disabled" instead — this keeps past delivery history and workflow configuration intact.
URL restrictions (what won't be accepted)
The URL is validated both when you save it and again right before each send. The following are rejected:
- Anything other than
https:(http:is not accepted) - Hostnames that resolve to private / loopback / link-local or similar internal addresses
- Numeric-IP and similar notations that try to work around the above
Even a URL that was accepted at registration time will be rejected on the next send if its DNS is later repointed to an internal address.
Organization-shared endpoints
If a sibling facility in the same organization has registered an endpoint, it appears in your list with an "Org-shared" badge. This is read-only — you cannot edit, disable, or manage the secret for it from your facility's screen (the URL itself is hidden; only an identifying hostname is shown). Events are still delivered to org-shared endpoints, so they do appear as a selectable destination when configuring a workflow.
Feature 2: Signing secret and rotation
Every request carries an HMAC signature so the receiver can verify it came from UnlockOS.
What's in the signature
| Header | Content |
|---|---|
X-UnlockOS-Signature |
sha256= followed by the signature (base64). During rotation, two may appear space-separated |
X-UnlockOS-Timestamp |
The send time (Unix seconds) included in what was signed |
X-UnlockOS-Endpoint-Id |
Identifies which endpoint (and therefore which secret) to verify against |
X-UnlockOS-Delivery-Id |
The idempotency key for this delivery (same as the body's eventId) |
X-UnlockOS-Delivery-Attempt |
Which attempt this is (starts at 1, increases on retry) |
X-UnlockOS-Event-Type |
Same value as the body's eventType |
The signature is base64(HMAC-SHA256(signing_secret, "timestamp" + "." + "raw request body bytes")). To verify it on your side:
- Use
X-UnlockOS-Endpoint-Idto look up the matching secret in your own records (don't use the organization/facility IDs inside the body to choose the key — anything read before verification can't be trusted) - Recompute the same value using that secret, the received
X-UnlockOS-Timestamp, and the raw received body bytes (not a JSON value re-serialized after parsing) - Compare it against
X-UnlockOS-Signature— there may be more than onesha256=value; accept if any one of them matches (assuming there is only ever one will break delivery during key rotation, described below) - Confirm
X-UnlockOS-Timestampisn't far from the current time (roughly within 5 minutes) to guard against replay - Even after signature and timestamp checks pass, discard the request if you've already processed this
eventIdbefore (delivery is at-least-once, so the same event can arrive more than once)
Rotating the secret
Clicking "Rotate secret" issues a new secret while keeping the previous one valid for one generation, so verification keeps working while you update the receiving side — no downtime. You'll be asked to confirm before it runs.
"Reveal secret" shows the current secret again on demand, but repeating it too quickly within a short window is temporarily rate-limited as an anti-abuse measure. Wait a bit and try again.
Feature 3: Payload shape
The request body (envelope) sent looks like this. Organization, facility, and environment fields are assembled by the system automatically and cannot be edited by the facility owner — letting tenant identifiers be edited would risk a receiver mistaking one tenant's event for another's due to typos or omissions.
{
"schemaVersion": "1",
"eventType": "reservation.created",
"eventId": "idempotency key for this delivery, unchanged across retries",
"sentAt": "send time (ISO 8601)",
"environment": "production | staging | development",
"endpoint": { "id": "the endpoint's id" },
"organization": { "id": "...", "name": "..." },
"facility": { "id": "...", "name": "...", "slug": "...", "timezone": "Asia/Tokyo" },
"workflow": { "id": "...", "key": "...", "anchorEvent": "..." },
"source": { "type": "reservation" },
"data": { }, // shape varies per eventType
"customData": { } // free-form field the facility owner can add (not yet exposed in the UI — planned)
}
- Guest name, email, and phone number are never included. Personal information is minimized by design
environmentlets you tell staging and production apart even if they post to the same URLfacility.slugcan change, so route onfacility.id/organization.idinstead
v1 event list
eventType |
Fires when |
|---|---|
reservation.created |
A reservation is created |
reservation.cancelled |
A reservation is cancelled |
reservation.extended |
A reservation is extended |
reservation.approval_pending |
A reservation enters the pending-approval state |
reservation.started |
The reservation's start time is reached |
reservation.ended |
The reservation's end time is reached |
checkin.completed |
Check-in is completed |
checkout.completed |
Check-out is completed |
payment.completed |
Payment is completed |
Which event triggers a Webhook send is set by choosing the anchor event on the workflow's edit screen in Notification Workflows.
Feature 4: Retries and failure behavior
| Response | Behavior |
|---|---|
| 2xx | Success. Recorded as "sent" on the History tab |
| 429 / 408 | Treated as transient and retried |
| Other 4xx | Permanent failure. Not retried (treated as a misconfigured destination that won't change on resend) |
| 3xx (redirect) | Permanent failure. Redirects are never followed (to avoid opening a bypass path) |
| 5xx / timeout / connection error | Treated as transient and retried (up to 5 attempts, then marked "failed") |
- The response timeout is 5 seconds
eventIdand the signed body never change across retries — only the attempt count (X-UnlockOS-Delivery-Attempt) increases- Delivery results can be checked on the History tab of Notification Workflows, filtered to channel "Webhook" (the same screen used for every other channel)
Feature 5: Billing
The Webhook channel is free. Since it's a plain HTTP request with no third-party cost, it isn't metered the way SMS, Voice, and Speaker are.
Feature 6: Choosing between channels
| Channel | Recipient | Eligible for "Auto"? | Billing |
|---|---|---|---|
| Email / LINE / SMS | A guest or an individual admin | Yes (routed automatically to whichever is reachable) | Email/LINE free, SMS metered |
| Voice / Speaker | A phone call / an in-space speaker | No (explicit selection only) | Metered |
| Webhook | An external system's endpoint (not a person) | No (explicit selection only) | Free |
Webhook isn't a channel for notifying a person — it's a channel for feeding events into a system. That's why it's never chosen by "Auto"; you must explicitly select "Webhook" on the workflow's edit screen and pick a destination endpoint.
FAQ
Q: Can I see the URL of an org-shared endpoint?
A: No. Your facility's screen only shows an identifying hostname — the full URL (which, without a signature check, is itself sensitive) is hidden.
Q: I want to delete an endpoint
A: Deletion isn't supported. Switch it to "disabled" instead — this preserves past delivery history and workflow configuration.
Q: Which events trigger Webhook?
A: Whatever anchor event is set on a workflow whose channel is "Webhook" in Notification Workflows. This works for both standard and custom workflows.
Q: Where do I set customData?
A: There's no input field for it in the current admin UI yet (planned for a future update). Right now only the system-assembled standard fields are included in the body.
Q: I lost my signing secret
A: Use "Reveal secret" to show it again. This requires owner-level access, and repeating it in quick succession is rate-limited.
Q: Can I register a URL that redirects?
A: You can register it, but redirects are never followed at send time — a 3xx response is treated as a permanent failure. Register the final, non-redirecting URL instead.
Related pages
- Notification Workflow - configure the anchor event and channel selection here
- Speaker Notification Channel