v4/Reference

Security

This page covers the two trust boundaries in a Mosler integration and how to secure each:

  1. Inbound — requests you send to Mosler, authenticated with an API key.
  2. Outbound — callbacks Mosler sends to you, which you verify with a signature.

API keys (inbound)

Every request to the webhook and API services authenticates with a company-scoped key in the apikey header:

curl https://api.mosler.in/api/v4/bookings/RES-20250718-001/access \
  -H "apikey: YOUR_MOSLER_API_KEY"

Keys have these properties:

PropertyBehaviour
Company-scopedA key resolves to exactly one company. Every read and write is restricted to that company's data — there is no cross-company access.
RevocableA key can be deactivated server-side. A revoked key immediately returns 401 API key has been revoked.
ExpiringA key may carry an expiry. After it passes, requests return 401 API key has expired.
AuditedEach use updates a last_used_at timestamp, so unused or leaked keys are easy to spot.

Handling keys safely

  • Never embed a key in client-side code (browser, mobile app, public repo). Keys belong on your server only.
  • Send keys over HTTPS only. All Mosler endpoints are TLS-terminated; never call an http:// origin.
  • Rotate on suspicion. If a key may have leaked, issue a new one in the Admin Portal and revoke the old one — revocation takes effect immediately.
  • Use one key per integration where practical, so you can revoke a single consumer without disrupting the others.

X-Company-Id: If you send this header, it must match the company your key belongs to or the request is rejected with 403. You normally don't need to set it — the company is derived from the key itself.


Webhook signatures (outbound)

When Mosler delivers an access callback to your endpoint (see Delivering Access), it signs the request so you can prove it came from Mosler and wasn't tampered with in transit.

Every signed callback carries:

HeaderValue
X-Mosler-SignatureHex-encoded HMAC of the raw request body, using your shared secret.
X-Mosler-EventThe event type, e.g. access.provisioned, access.revoked, access.failed.
Content-Typeapplication/json

The signature is computed as HMAC-SHA256(secret, rawBody) over the exact bytes of the JSON body — so you must verify against the raw body, before any parsing or re-serialisation.

Verifying a callback

import { createHmac, timingSafeEqual } from 'crypto';

// Use the raw request body — NOT a re-stringified object.
function verifyMoslerSignature(rawBody, signatureHeader, secret) {
    const expected = createHmac('sha256', secret).update(rawBody).digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signatureHeader || '');
    // Constant-time compare to avoid leaking timing information.
    return a.length === b.length && timingSafeEqual(a, b);
}

// Express example — capture the raw body for verification.
app.post(
    '/mosler/callbacks',
    express.raw({ type: 'application/json' }),
    (req, res) => {
        const ok = verifyMoslerSignature(
            req.body, // Buffer of raw bytes
            req.header('X-Mosler-Signature'),
            process.env.MOSLER_CALLBACK_SECRET
        );
        if (!ok) return res.status(401).send('bad signature');

        const event = JSON.parse(req.body.toString('utf8'));
        // … handle event.event: access.provisioned | revoked | failed
        return res.status(200).send('ok');
    }
);

Always compare in constant time. Use crypto.timingSafeEqual (or your platform's equivalent) rather than ===, so an attacker can't recover the signature byte-by-byte through timing.

Other auth strategies

HMAC is the recommended default, but a callback can instead be configured to authenticate with a bearer token, basic auth, or a static API-key header — whichever your receiving endpoint expects. Whatever the strategy, the secret is shared only between Mosler and your endpoint; pick HMAC unless your gateway forces one of the others.


Secret rotation

You can rotate a callback secret without dropping a single event. During a rotation window Mosler signs with the current secret, while your endpoint should accept a signature that matches either the new or the previous secret:

function verifyWithRotation(rawBody, sig, current, previous) {
    return (
        verifyMoslerSignature(rawBody, sig, current) ||
        (previous && verifyMoslerSignature(rawBody, sig, previous))
    );
}

Once you've confirmed all in-flight callbacks are signed with the new secret, retire the previous one. This makes rotation a zero-downtime operation entirely under your control on the receiving side.


Replay and idempotency

Callbacks can, in rare cases, be delivered more than once (a network blip during retry, for example). Protect against double-processing:

  • Key off the event identity. Each access event references your referenceId and an event type — treat the pair as an idempotency key and ignore a second arrival you've already applied.
  • Make handlers idempotent. Granting access that's already granted, or revoking access that's already revoked, should be a no-op on your side.
  • Respond 2xx only after you've durably recorded the event. A non-2xx response tells Mosler to retry, so acknowledge only once you've safely handled it.

Checklist

  • API keys live server-side, never in client code or version control.
  • All calls use HTTPS.
  • Callback endpoint verifies X-Mosler-Signature against the raw body.
  • Signature comparison is constant-time.
  • Rotation logic accepts the previous secret during the window.
  • Handlers are idempotent and keyed on referenceId + event type.