Creating Bookings
You drive a guest's whole access lifecycle — grant on booking, revoke on cancel/checkout — by sending booking events to Mosler Direct. One endpoint handles create, update, and cancel; the action field says which.
Events are processed asynchronously: Mosler accepts your event with 202 Accepted and an eventId, then provisions device access in the background. You can poll the event to confirm completion.
v3 → v4 difference: v3 used synchronous REST —
POST /api/v3/bookingreturned the created booking directly. v4 is fully asynchronous: the202response only confirms the event was accepted; you must poll theeventIdor wait for a push notification to know when access is ready. Thereference_idyou choose replaces v3's internal booking IDs as the key used across the entire lifecycle — create, update, cancel, and credential retrieval all key off this same value.
Endpoint
POST https://webhook.mosler.in/webhook/t/<your-token>
The token in the URL is the whole credential — no other auth is needed. Prefer not to put the credential in the URL? Use the header alternative: POST /webhook/generic with apikey, X-Company-Id, and X-Site-Id.
Whether you name the site in the body depends on the token's scope: a company-level token carries only your company, so each payload names its own site; a site-level token pins the site in the URL. See identifying the site.
| Header | Type | Required | Description |
|---|---|---|---|
Content-Type | string | Yes | application/json |
X-Correlation-Id | string | No | Your own trace ID, echoed in Mosler logs |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reference_id | string | Yes | Your booking identifier. Used for idempotency and to match follow-up events (update, cancel). Reuse the same value across a booking's lifecycle. |
action | string | Yes | Lifecycle action — see mapping. |
site / site_id | string | Conditionally | Which property this booking belongs to. Required on a company-level token URL; ignored on a site-level one. Mosler site _id or your own external_id — see identifying the site. |
building / building_id | string | No | Building reference (Mosler _id or your external_id). Recorded on the event; not used to resolve the room — see building and floor. |
floor / floor_id | string | No | Floor reference (Mosler _id or your external_id). Recorded on the event; not used to resolve the room — see building and floor. |
room / room_number | string | Conditionally | Room name as it appears in the Location API. Required for create / update / checkin / checkout; optional for cancel. |
bed / bed_number | string | No | Bed name within the room, for shared/dormitory properties. |
room_id / bed_id | string | No | Your own location ID, if you've attached external IDs. Resolved before name matching — rename-proof. |
check_in / start_date | string | Yes | Check-in date/time. ISO 8601 preferred. |
check_out / end_date | string | Yes | Check-out date/time. |
guest.name | string | No | Guest full name. |
guest.email | string | No | Guest email. Provide email or phone (both recommended). |
guest.phone | string | No | Guest phone with country code, e.g. +919991234567. |
access_type | string | string[] | No | Credential type(s) to provision. Default: EKEY. See access types. |
card_id | string | No | RFID card identifier — required when access_type includes CARD. |
Guest contact drives delivery. If you want Mosler to send the credential straight to the guest (see Delivering Access), include
guest.phoneand/orguest.email.
Identifying the site (company-level URLs)
If you run more than one property you can use one company-level URL for all of them instead of one URL per site. The token then carries only your company, and each payload says which property the booking is for:
{
"reference_id": "RES-20250718-001",
"action": "create",
"site": "6530f9dce0c1bd73ded0d1be",
"room_number": "101",
"check_in": "2025-07-18T14:00:00.000Z",
"check_out": "2025-07-22T11:00:00.000Z"
}
site (alias: site_id) accepts two identifier styles. The shape of the value decides which one is used:
| You send | Resolved by |
|---|---|
Exactly 24 hex characters — a Mosler site _id from the Location API | Direct lookup. The site must belong to your company — another company's id is refused. |
| Anything else | Your own site external_id, attached to the site and unique within your company. |
The two are not tried in sequence: a 24-hex value is only ever looked up as a Mosler _id, so avoid external_id values that happen to be 24 hex characters — they will never match.
Site names are not matched. A human-readable name like "Delhi" will not resolve — use an _id or an external_id.
On a site-level URL the site is already pinned by the token, so any site in the body is ignored. Sending it anyway is harmless, which lets you use the same payload builder for both URL shapes.
Unresolved sites
A site reference Mosler can't match is not an error you have to handle at send time. The event is stored as UNMAPPED, nothing is provisioned, and you still get a 202 — so a property that hasn't been mapped yet never causes your system to disable the webhook:
{
"success": true,
"eventId": "a3f9d2e1-84c7-4b56-9f13-0d2e4c8a1b7f",
"status": "UNMAPPED",
"message": "Site could not be resolved (site_external_id_not_found) — event stored for review and not processed"
}
| Reason in the message | What happened |
|---|---|
missing_site_ref | No site / site_id in the payload, on a company-level URL |
site_objectid_not_found | The 24-hex id doesn't exist |
site_cross_company | The id exists but belongs to another company |
site_external_id_not_found | No site in your company carries that external_id |
The event is kept in full. Once the site is mapped, Mosler replays it — nothing is lost and you do not need to resend. If you'd rather catch these yourself, poll the eventId (below) or list events filtered by status.
Building and floor
building / floor (aliases building_id / floor_id) accept the same two identifier styles as site and are always optional. Mosler records them on the event, but room resolution does not use them — room and bed references are already unique within a site, so naming the building or floor cannot change which lock is provisioned. Send them if it makes your payloads easier to trace against your own system; leave them out otherwise.
Action mapping
The action string is normalised — casing and separators (_, -) are ignored.
| Your action string | Maps to |
|---|---|
create, new, reservation | BOOKING_CREATE |
cancel | BOOKING_CANCEL |
checkin, check_in, check-in | BOOKING_CHECKIN |
checkout, check_out, check-out | BOOKING_CHECKOUT |
roommove, room_move, roomchange | BOOKING_ROOMMOVE |
datechange, date_change, extend | BOOKING_DATECHANGE |
| anything else | BOOKING_UPDATE |
Create a booking
curl -X POST https://webhook.mosler.in/webhook/t/<your-token> \
-H "Content-Type: application/json" \
-d '{
"reference_id": "RES-20250718-001",
"action": "create",
"room_number": "101",
"check_in": "2025-07-18T14:00:00.000Z",
"check_out": "2025-07-22T11:00:00.000Z",
"guest": {
"name": "Jane Smith",
"email": "jane.smith@example.com",
"phone": "+919991234567"
},
"access_type": "EKEY"
}'
Success — 202 Accepted
The event is validated, stored, and queued. Provisioning happens asynchronously.
{
"success": true,
"eventId": "a3f9d2e1-84c7-4b56-9f13-0d2e4c8a1b7f",
"status": "QUEUED",
"message": "Event received and queued for processing"
}
Save the eventId.
Validation error — 400 Bad Request
{
"success": false,
"errors": [
"reference_id is required",
"check_in or start_date is required"
],
"message": "Invalid payload"
}
Duplicate — 202 (idempotent)
If the same reference_id and action arrive within 60 seconds, Mosler returns the original event instead of processing it twice.
{
"success": true,
"eventId": "a3f9d2e1-84c7-4b56-9f13-0d2e4c8a1b7f",
"status": "QUEUED",
"message": "Duplicate event detected, returning existing event"
}
Update or cancel
Send the same reference_id with a different action. For a cancel, room and date fields are optional — Mosler looks up the booking by reference_id and revokes its access.
curl -X POST https://webhook.mosler.in/webhook/t/<your-token> \
-H "Content-Type: application/json" \
-d '{ "reference_id": "RES-20250718-001", "action": "cancel" }'
To change dates or room, send action: "update" (or a date-change/room-move action) with the new values and the original reference_id.
Tracking an event
Provisioning is asynchronous. Poll the event by its eventId to confirm it reached COMPLETED. In production, wait ~5 seconds before the first check.
curl https://webhook.mosler.in/webhook/events/a3f9d2e1-84c7-4b56-9f13-0d2e4c8a1b7f \
-H "apikey: YOUR_MOSLER_API_KEY"
{
"success": true,
"eventId": "a3f9d2e1-84c7-4b56-9f13-0d2e4c8a1b7f",
"status": "COMPLETED",
"message": null
}
To see every event for a booking (create, cancel, …) use GET /webhook/events/reference/:referenceId. Full status semantics, retries, and the dead-letter flow are documented in Event Lifecycle.
Once status is COMPLETED, the credential exists. Fetch it via Retrieving Access, or let Mosler push it for you — see Delivering Access.
Common errors
| Error | Likely cause |
|---|---|
reference_id is required | Missing reference_id in body |
room or room_number is required for this action | Missing room on a create/update/checkin/checkout |
status: UNMAPPED, message Site could not be resolved (…) | The payload's site reference didn't resolve — see identifying the site. Returned as 202, not an error status. |
site_id is required for this provider (400) | A company-level URL was used for a PMS provider (eZee, Hotelogix). Those require a site-level token. |
status: FAILED, message Room not found | Room name doesn't match any room in the site — re-sync via the Location API |
status: FAILED, message Site not found | Token/site mismatch — the booking's site isn't configured for this company |