v4/Mosler Direct

Delivering Access

Retrieving Access is the pull model — you ask Mosler for a credential when you need it. Delivery is the push model — Mosler sends the credential to you, or directly to the guest, the moment provisioning finishes. No polling required.

There are two delivery modes; you can use either or both.


Mode 1 — Webhook event caller

Configure a callback URL on your Integration in the Mosler client portal. When the worker finishes handling a booking event, Mosler POSTs an authenticated event to that URL. Your endpoint records the credential and replies 2xx.

Booking event COMPLETED
        │
        ▼
Mosler  ──POST {signed access.provisioned}──►  https://your-system/mosler/callback
        ◄────────────── 200 OK ──────────────

One callback per accepted event. Every event we answer with 202 Accepted reaches exactly one terminal state, and if you have a callback configured you receive exactly one callback describing it — success or failure. Because booking ingestion is asynchronous, the 202 only confirms we accepted the event; the callback is the only place the outcome is reported.

That includes outcomes where nothing was provisioned: a room we can't find, a stay that already ended, a duplicate you resent. Those arrive as access.failed with a machine-readable code rather than silence, so you never have to infer an outcome from a callback that never came.

Event payload
FieldTypeDescription
schemaVersionnumberPayload contract version, currently 1. Pin or range-check this — new optional fields may be added under the same major.
eventstringaccess.provisioned, access.revoked, or access.failed
referenceIdstringYour booking reference
occurredAtstringISO 8601 UTC timestamp
bookingobject{ id, status, startDate, endDate }. Omitted on an access.failed raised before a booking exists — use referenceId as the key.
access[]arraySame per-device shape as GET …/accesstype, the credential for that type (passcode, cardId, or lockData + lockMac for e-keys), device/room/bed, validity. Present only on access.provisioned — see which fields arrive on which event.
errorobjectOnly on access.failed: { code, message } describing why. code is machine-readable — see failure codes. Replaces access[].
Which fields arrive on which event

Branch on event and read the fields that event carries. Do not require access[] on every callback — a validator that does will reject two of the three event types, and because a 4xx is a definitive rejection we do not retry it, so the event is dead-lettered and you never receive it.

Fieldaccess.provisionedaccess.revokedaccess.failed
booking✅ always✅ always⚠️ omitted when the failure occurred before a booking existed
access[]✅ always❌ never — the credentials are gone by definition❌ never
error❌ never❌ never✅ always
{
    "schemaVersion": 1,
    "event": "access.provisioned",
    "referenceId": "RES-20250718-001",
    "occurredAt": "2025-08-01T08:30:02.114Z",
    "booking": {
        "id": "6576fe78632cfff91c62a3c2",
        "status": "active",
        "startDate": "2025-08-01T08:30:00.000Z",
        "endDate": "2025-08-05T05:30:00.000Z"
    },
    "access": [
        {
            "deviceName": "Room 101 Door",
            "roomNumber": "101",
            "bedNumber": null,
            "type": "passcode",
            "passcode": "482910",
            "validFrom": "2025-08-01T08:30:00.000Z",
            "validUntil": "2025-08-05T05:30:00.000Z"
        },
        {
            "deviceName": "Room 101 Door",
            "roomNumber": "101",
            "bedNumber": null,
            "type": "ekey",
            "lockData": "Rz7oNSAH1No3YXRiG0j8FKB3xBjhuILrkTjWkOMw...",
            "lockMac": "AA:BB:CC:DD:EE:FF",
            "validFrom": "2025-08-01T08:30:00.000Z",
            "validUntil": "2025-08-05T05:30:00.000Z"
        }
    ]
}

E-key credentials arrive in full. For type: "ekey" the callback carries lockData and lockMac inline — everything the Mobile Key SDK needs. You do not have to call back to the API to complete an e-key.

An access.failed event carries an error object instead of access[]:

{
    "schemaVersion": 1,
    "event": "access.failed",
    "referenceId": "RES-20250718-001",
    "occurredAt": "2025-08-01T08:30:02.114Z",
    "booking": {
        "id": "6576fe78632cfff91c62a3c2",
        "status": "active",
        "startDate": "2025-08-01T08:30:00.000Z",
        "endDate": "2025-08-05T05:30:00.000Z"
    },
    "error": {
        "code": "NO_DEVICE_BOUND",
        "message": "No lock is mapped to room 101 at this site."
    }
}

For a failure raised before a booking exists — an unmapped room, or a cancel for a reference we've never seen — there is no booking to describe, so the booking block is omitted and the event is keyed by your own referenceId alone:

{
    "schemaVersion": 1,
    "event": "access.failed",
    "referenceId": "RES-20250718-001",
    "occurredAt": "2025-08-01T08:30:02.114Z",
    "error": {
        "code": "LOCATION_NOT_FOUND",
        "message": "No room or bed matching RoomName: 101 in site 6530f9dce0c1bd73ded0d1be exists in Mosler. Check the room mapping for this site."
    }
}
Failure codes

error.code is machine-readable and safe to branch on. Codes are append-only — new ones may appear under schemaVersion: 1, so treat an unrecognised code as a generic failure rather than rejecting the payload.

CodeMeaningWho fixes it
LOCATION_NOT_FOUNDThe room or bed in your payload doesn't exist in Mosler for that siteYou — re-sync via the Location API and resend
BOOKING_ALREADY_ENDEDThe stay had already ended when the event arrived, so no credential was issuedYou — check the check_in / check_out dates you sent
BOOKING_NOT_FOUNDA cancel or update arrived for a reference_id Mosler has never seenYou — usually a reference that drifted between systems
NO_ACTIVE_CREDENTIALSThe booking exists but holds no live credential right nowEither — often follows a cancellation or an expired stay
NO_DEVICE_BOUNDThe location resolved, but no lock is mapped to itMosler — contact us; nothing to change on your side
GRANT_FAILEDOne or more locks rejected the credentialMosler — usually a device or gateway problem
GRANT_ERRORProvisioning failed before completingMosler

The "who fixes it" column is the useful split: LOCATION_NOT_FOUND, BOOKING_ALREADY_ENDED and BOOKING_NOT_FOUND are almost always something in your payload and are worth surfacing to whoever operates your booking flow. The rest are ours.

Verifying the signature

By default, each call carries an X-Mosler-Signature header: an HMAC-SHA256 of the raw request body, keyed with the callback secret stored on your Integration. Recompute it and compare before trusting the payload.

import crypto from 'crypto';

function isValid(rawBody, signatureHeader, secret) {
    const expected = crypto
        .createHmac('sha256', secret)
        .update(rawBody) // the exact bytes received
        .digest('hex');
    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signatureHeader)
    );
}
Outbound auth options

HMAC signing is the default. On your Integration you can instead choose Bearer token, Basic auth, a custom API-key header, or none — Mosler attaches the configured credential to every callback (including the test.ping). Pick the scheme your endpoint expects when you set up the Integration in the client portal.

Delivery guarantees
  • Retries: non-2xx responses (and timeouts) are retried with exponential backoff. After the final attempt the event is dead-lettered and surfaced for manual replay.
  • Idempotency: retries reuse the same payload. De-duplicate on (referenceId, event, occurredAt) — your endpoint may receive a delivery more than once.
  • Ordering: not guaranteed under retries. Treat access.revoked as authoritative over an earlier access.provisioned for the same booking by comparing occurredAt.
Verifying your endpoint before go-live

Before you take a real booking, confirm your callback URL and signature check work end-to-end. Fire a signed test.ping at your registered URL:

POST /api/v4/callbacks/test

curl -X POST https://api.mosler.in/api/v4/callbacks/test \
  -H "apikey: YOUR_API_KEY"

Mosler delivers a test.ping event to your registered callback URL — authenticated with the same scheme as real events — and returns the HTTP result it observed from your endpoint:

{
    "success": true,
    "delivered": true,
    "url": "https://your-system/mosler/callback",
    "responseStatus": 200,
    "authType": "hmac"
}

The test.ping body matches the standard envelope so your verifier exercises the real code path:

{
    "schemaVersion": 1,
    "event": "test.ping",
    "occurredAt": "2025-08-01T08:30:02.114Z",
    "message": "Mosler callback verification ping."
}

No booking is created. Use this to validate your endpoint and signature verification without touching a real reservation.


Mode 2 — Direct guest messaging

Let Mosler deliver the credential straight to the guest — no callback handling, no app on your side. When provisioning completes, Mosler sends the passcode (or a mobile-key link) to the guest over WhatsApp and/or email using Mosler templates.

To enable it, include guest contact details on the booking and the property's messaging preference is configured in the Mosler Admin portal:

{
  reference_id: "RES-20250718-001",
  action: "create",
  room_number: "101",
  guest: {
    name: "Jane Smith",
    email: "jane.smith@example.com",   // → email delivery
    phone: "+919991234567"             // → WhatsApp/SMS delivery
  },
  access_type: "PASSCODE"
}

The guest receives a Mosler-branded message with their PIN (or a link to open their mobile key) and the stay window. Revocation on checkout/cancel can likewise notify the guest that access has ended.


Choosing a mode

Webhook event callerDirect guest messaging
Who receives the credentialYour systemThe guest
You buildA callback endpointNothing — just send guest contact
Best whenYou own the guest experience/appYou want Mosler to handle delivery

You can combine them: receive the callback for your records and have Mosler message the guest.


Setup

Mode 1 is self-service: create an Integration in the Mosler client portal and set its callback URL, outbound auth scheme, and scope — then verify it end-to-end with POST /api/v4/callbacks/test. Mode 2 (direct guest messaging) channel preferences are configured per company/site in the Mosler Admin portal.