Security
This page covers the two trust boundaries in a Mosler integration and how to secure each:
- Inbound — requests you send to Mosler, authenticated with an API key.
- 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:
| Property | Behaviour |
|---|---|
| Company-scoped | A key resolves to exactly one company. Every read and write is restricted to that company's data — there is no cross-company access. |
| Revocable | A key can be deactivated server-side. A revoked key immediately returns 401 API key has been revoked. |
| Expiring | A key may carry an expiry. After it passes, requests return 401 API key has expired. |
| Audited | Each 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 with403. 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:
| Header | Value |
|---|---|
X-Mosler-Signature | Hex-encoded HMAC of the raw request body, using your shared secret. |
X-Mosler-Event | The event type, e.g. access.provisioned, access.revoked, access.failed. |
Content-Type | application/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
referenceIdand 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
2xxonly 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-Signatureagainst 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.