Browse docs
Frontelio Access · Integrations

Already have HID, Kisi, Salto, Brivo, or Latch? We integrate.

If you already run a commercial access platform, you can keep it. Frontelio imports its doors and records its access events in your audit log, next to the taps from your Frontelio-native readers.

Frontelio ships a generic reader integration with per-provider adapters. Each adapter handles the provider-specific authentication, door discovery, and inbound-webhook parsing and signature verification. Once an integration is configured, the doors from the external platform appear in your admin UI alongside Frontelio-native Zones, ready to map.

Today there are five adapters: Kisi (GA), and Salto, Brivo, HID Origo, Latch (all BETA). The general flow is the same across all providers; the credentials, the signature header, and the event types Frontelio recognises differ.

General flow

Regardless of provider, you'll follow these steps. Frontelio Access must be enabled on your plan (Growth or higher), and you must be signed in as an owner, company admin, HR manager, or area manager.

  1. Mint credentials at the provider. Each one has its own way — API key, OAuth client, or signing key. Per-provider details below.
  2. Create the integration. In /admin/access → Integrations, click New integration, pick the provider, and give it a display name (e.g. "Kisi - DIFC HQ"). The dialog has credential fields for Kisi only. For Salto, Brivo, HID Origo, and Latch, create the integration with POST /access/integrations instead (see Creating an integration through the API). Either way, Frontelio checks the credentials with the provider before it saves anything.
  3. Copy the webhook URL and secret. Frontelio shows them once, right after the integration is created. The secret cannot be shown again (see the callout below).
  4. Configure the webhook at the provider, using that URL, and the secret as the signing secret. The provider will start POSTing access events to us in real time. Frontelio drops any delivery that is not signed with the secret.
  5. Test. Click Test on the integration row. Frontelio calls the provider with the stored credentials and reports Connection OK, or Connection failed followed by the provider's error. This checks the credentials only; it does not send a webhook.
  6. Sync doors. Click Doors on the row, then Sync now. We pull the list of doors the provider exposes for this account; they appear as "unmapped". Sync again whenever doors are added at the provider.
  7. Map doors to Zones. For each external door, pick the Frontelio Zone it represents. Mapping is optional for the audit log: events from an unmapped door are still recorded, just without a Zone.
  8. Verify the wiring with a real tap or a signed test delivery — see Verify the wiring.

Creating an integration through the API

The console dialog collects Kisi credentials only. Every provider can be created through the API, and it is the only route for Salto, Brivo, HID Origo, and Latch. Sign in as described in the API overview to get a bearer token, then:

bash
curl -sS -X POST https://api.frontelio.com/api/v1/access/integrations \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d @integration.json

integration.json is the body shown in each provider section below: provider is one of KISI, SALTO, BRIVO, HID_ORIGO, LATCH (upper case), displayName is 2 to 80 characters, and config holds that provider's credentials. A bad credential is a 400 with Connection test failed: …; nothing is saved. The optional baseUrl and authUrl overrides must be public https:// addresses with no credentials, query string or fragment, and a redirect from that host is never followed (a 400). The response is 201:

201 Created
{
  "id": "0b1c9a7e-3f41-4d0e-9a3a-6f2d8c5b7e11",
  "tenantId": "8d2f6a10-52c4-4b7e-9c1d-3a4b5c6d7e8f",
  "provider": "KISI",
  "displayName": "Kisi - DIFC HQ",
  "configPreview": {
    "apiKey": "***redacted***",
    "placeId": "12345"
  },
  "webhookSecret": "a3f1c2d4e5b60718293a4b5c6d7e8f90a1b2c3d4e5f60718",
  "status": "ACTIVE",
  "lastSyncAt": null,
  "lastErrorAt": null,
  "lastError": null,
  "doorMappingCount": 0,
  "createdAt": "2026-09-20T09:00:00.000Z",
  "updatedAt": "2026-09-20T09:00:00.000Z"
}

webhookSecret appears here and nowhere else. Keys, secrets, tokens, and passwords in config are stored AES-256-GCM encrypted at rest and come back as ***redacted***; identifiers such as clientId and organizationId are returned as-is. The other integration endpoints (GET, PATCH, DELETE, test, sync, door mapping) are in the /access API reference.

Kisi (GA)

Kisi is the most common Frontelio Access integration — cloud-native, well-documented API, and the team responds to support quickly. The adapter is GA (shipped in v1.24.0).

Credentials you need

  • Kisi API key (apiKey). Create one in Kisi (see Kisi's API documentation). Frontelio uses it to list your places and doors, so it must be able to read both.
  • Place ID (placeId, optional). One integration syncs the doors of one Kisi place. If you leave it blank, Frontelio uses the first place your key can see (the first result of GET https://api.kisi.io/places); it does not sync all places. To use a different place, or to cover several, create one integration per place and enter each place's ID. List your places and their IDs with curl -H "Authorization: KISI-LOGIN <api key>" https://api.kisi.io/places.

There is no organisation ID: Frontelio never asks for or sends one.

Setup

  1. In Frontelio: /admin/access → Integrations → New integration → pick "Kisi". Paste the API key and, optionally, the Place ID. Or use the API:
    KISI body
    {
      "provider": "KISI",
      "displayName": "Kisi - DIFC HQ",
      "config": {
        "apiKey": "kisi_live_xxxxxxxxxxxxxxxx",
        "placeId": "12345"
      }
    }
  2. Copy the webhook URL and webhook secret we display. This is the only time the secret is shown.
  3. In Kisi, add an event webhook (Kisi documents these as event webhooks). Paste our URL. Paste our secret as the signature key: Kisi signs the request body with it and sends the signature in X-Signature. The key is optional in Kisi, but you must set it: Frontelio drops every delivery that is not signed. Choose the event types listed below.
  4. Back in Frontelio, open Doors and click Sync now. You'll see the Kisi doors appear. Map each to a Frontelio Zone.

Events Frontelio records

Frontelio classifies a Kisi delivery by its type. A type ending in .access, or equal to unlock.success or lock.unlocked, is recorded as a GRANT. A type containing denied, failure, or reject is recorded as a DENY. Any other type is acknowledged and ignored. For example:

  • ble.access — GRANT
  • card.access — GRANT
  • code.access — GRANT
  • unlock.success — GRANT
  • lock.unlocked — GRANT
  • unlock.failure — DENY
  • access.denied — DENY
  • access.granted — ignored
  • user.invited — ignored

The delivery must also carry event_id, type, and lock.id (the door). Add user.email to link the row to a Frontelio user; without it, or with an email that matches nobody in your tenant, the row is still recorded, just without a user.

Once a delivery is accepted, the tap shows up in /admin/access → Audit alongside your native phone-tap events. Confirm it with a test tap or a signed test delivery (see Verify the wiring) before you rely on it.

Salto (BETA)

Salto KS is common in higher-end UAE hospitality and serviced offices. The adapter targets Salto KS Cloud, Salto's hosted platform, where doors are called access points. It does not include Salto Space or Salto Connect support.

Credentials you need

  • Salto OAuth client ID and secret (clientId, clientSecret). Request from your Salto reseller — they need to enable "3rd-party integration" on your account. The credentials are an OAuth 2.0 client-credentials grant; we exchange them for a short-lived access token when we need one.
  • Base URL (baseUrl, optional, API only). Defaults to https://clp-accept-user.my-clay.com/v1.1. That is the host the adapter was built against, which its own notes call Salto's staging/test cluster, and they add that production customers may be served from a regional cluster. Ask Salto which base URL applies to your account and pass it explicitly.

There is no site ID. One credential covers every access point it can see, and door sync lists all of them, so you create one integration per credential, not one per site.

Setup

  1. Create the integration with the API (the console dialog has no Salto fields):
    SALTO body
    {
      "provider": "SALTO",
      "displayName": "Salto - Marina Tower",
      "config": {
        "clientId": "abc-123",
        "clientSecret": "supersecret"
      }
    }
  2. Copy the webhook URL and secret from the response. Configure the webhook in Salto's admin console: paste our URL and our secret as the signing secret. Salto sends the signature in X-Salto-Signature.
  3. Open Doors, click Sync now, and map the doors to Zones. Same flow as Kisi.

Events Frontelio records

Salto deliveries look like { "event": { "type", "id", "data": { "user": { "email" }, "accesspoint": { "id" } } } }. The type decides the outcome:

  • access.granted — GRANT
  • access.denied — DENY
  • user.created — ignored

Brivo (BETA)

Brivo OnAir is a US-rooted access platform with a presence in UAE retail and offices. The adapter is BETA.

Credentials you need

  • Brivo API client ID and secret (clientId, clientSecret). Brivo issues these via their Developer Portal — you may need to file a ticket with Brivo support to enable third-party integration on your account.
  • Brivo API key (apiKey). Brivo requires your account's api-key on every API call, alongside the OAuth token, and Frontelio sends both. An integration without it is rejected.
  • Base URL and auth URL (baseUrl, authUrl, optional, API only). Default to https://api.brivo.com and https://auth.brivo.com; override only if Brivo gives your account different hosts.

There is no account ID field; Frontelio does not read one.

Setup

  1. Create the integration with the API (the console dialog has no Brivo fields):
    BRIVO body
    {
      "provider": "BRIVO",
      "displayName": "Brivo - Dubai HQ",
      "config": {
        "clientId": "abc-123",
        "clientSecret": "supersecret",
        "apiKey": "brivo-api-key-123"
      }
    }
  2. Copy the webhook URL and secret from the response. Configure the webhook in Brivo: paste our URL and our secret as the signing secret. Brivo sends the signature in X-Brivo-Signature.
  3. Open Doors, click Sync now, and map the doors to Zones.

Events Frontelio records

Brivo deliveries look like { "eventType": "access.event", "id", "verdict", "object": { "id" }, "actor": { "email" } }. Only eventType access.event is read; the verdict (not case-sensitive) decides the outcome:

  • ALLOWED — GRANT
  • GRANTED — GRANT
  • DENIED — DENY
  • REJECTED — DENY
  • PENDING — ignored

HID Origo (BETA)

HID Origo is HID's cloud-managed credential platform — if you're already standardized on HID readers (common in enterprise UAE), this is the integration to use.

Credentials you need

  • HID Origo organisation ID and API key (organizationId, apiKey). Issued via your HID partner account.
  • Signing key (signingKey). The RSA private key HID issues to your organisation at onboarding, as a PEM (-----BEGIN … PRIVATE KEY-----). Frontelio signs a short-lived (5 minute) RS256 token with it to authenticate its API calls, so an integration without it cannot be created.
  • Key ID (signingKeyId, optional). The key ID shown in the Origo portal, sent as the token's kid.
  • Base URL (baseUrl, optional, API only). Defaults to https://api.origo.hidglobal.com/credential-management/v2.

HID treats these as confidential: the API key and the signing key are stored AES-256-GCM encrypted at rest and never returned by the API.

Setup

  1. Create the integration with the API (the console dialog has no HID Origo fields). The PEM must go into the JSON as a single string with \n line breaks; jq -Rs . < origo-private-key.pem prints it that way.
    HID_ORIGO body
    {
      "provider": "HID_ORIGO",
      "displayName": "HID Origo - Head office",
      "config": {
        "organizationId": "org-12345",
        "apiKey": "hid-api-key-123",
        "signingKey": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
      }
    }
  2. Copy the webhook URL and secret from the response. Configure the webhook in HID Origo: paste our URL and our secret as the signing secret. HID sends the signature in X-Origo-Signature.
  3. Open Doors, click Sync now, and map the doors to Zones.

Events Frontelio records

HID deliveries look like { "eventId", "eventType", "user": { "email" }, "door": { "doorId", "siteId" } }. The eventType (not case-sensitive) decides the outcome:

  • ACCESS_GRANTED — GRANT
  • UNLOCK_SUCCESS — GRANT
  • ACCESS_DENIED — DENY
  • UNLOCK_FAILURE — DENY
  • ACCESS_REJECTED — DENY
  • CREDENTIAL_ISSUED — ignored

When the delivery carries door.siteId, the door is identified as siteId:doorId, the same form door sync uses, so mappings line up.

Latch (BETA)

Latch is a smart-access platform strong in multifamily residential — dominant in that vertical in the US, and a growing segment in the UAE. The adapter shipped in v1.31.0 and is BETA: it is built to Latch's published developer docs but has not yet been validated against a live customer credential, so we co-pilot every production rollout.

Credentials you need

  • Latch OAuth client ID and secret (clientId, clientSecret). Issued via Latch's developer programme. The credentials are an OAuth 2.0 client-credentials grant; we exchange them for a short-lived bearer token when we need one.
  • Building ID (buildingId, optional, API only). If you do not pass one, Frontelio lists the buildings your token can see when the integration is created (or its config is updated) and stores the first one. The Test button does not store anything. Door sync reads a single building per integration, so if the token spans several buildings, create one integration per building and pass buildingId explicitly. To change the building on an existing integration, send PATCH /access/integrations/<id> with the whole config, including the new buildingId.
  • Base URL (baseUrl, optional, API only). Defaults to https://api.latch.com.

Setup

  1. Create the integration with the API (the console dialog has no Latch fields):
    LATCH body
    {
      "provider": "LATCH",
      "displayName": "Latch - Marina Residences",
      "config": {
        "clientId": "abc-123",
        "clientSecret": "supersecret"
      }
    }
  2. Copy the webhook URL and secret from the response. Configure the webhook at Latch: paste our URL and our secret as the signing secret. Latch sends the signature in X-Latch-Signature (or X-Signature), as described under the receiver contract below.
  3. Open Doors, click Sync now, and map the doors to Zones. Same flow as Kisi.

Events Frontelio records

Latch deliveries look like { "event": { "type", "id", "user": { "email" }, "door": { "id" } } }. The type decides the outcome:

  • door.unlock — GRANT
  • door.unlocked — GRANT
  • door.denied — DENY
  • door.deny — DENY
  • firmware.update — ignored

Inbound webhook receiver

Every integration in Frontelio exposes a unique inbound webhook URL. The contract:

POST /access/webhooks/<integrationId>
POST https://api.frontelio.com/api/v1/access/webhooks/0b1c9a7e-3f41-4d0e-9a3a-6f2d8c5b7e11
Content-Type: application/json
X-Kisi-Signature: lowercase-hex HMAC-SHA256 of the raw request body,
                  keyed by the integration's webhook secret

{
  ...provider-specific event payload...
}

No Authorization header is needed: the signature is the authentication. Each provider sends it in its own header:

  • Kisi — X-Kisi-Signature
  • Brivo — X-Brivo-Signature
  • HID Origo — X-Origo-Signature
  • Latch — X-Latch-Signature
  • Salto — X-Salto-Signature

For any provider, X-Signature is accepted as a fallback (Kisi itself sends the signature in that header). Only the header that matches the integration's provider, or X-Signature, counts. The signature is the HMAC-SHA256 of the exact raw request bytes, written as lowercase hex. The key is the webhook secret used as text — the 48 hex characters as they are, not decoded into bytes.

What happens to a delivery, in order:

  1. Integration lookup. An unknown integration ID, or an integration you have paused, records nothing.
  2. Signature check. A delivery with no signature, or a wrong one, is dropped: nothing is recorded. (Only the demo "Mock" provider accepts unsigned deliveries.)
  3. Parsing. The adapter's parseWebhook method normalises the provider event. Event types Frontelio does not recognise (see each provider above) are ignored.
  4. Recording. The door is matched to a Zone through your door mappings, and the user is matched by email within your tenant. One audit row is written with source EXTERNAL_<PROVIDER>, event type GRANTED (for a GRANT) or DENIED_NO_GRANT (for a DENY), and the provider's door ID as the reader ID. The same event ID for the same door twice within 2 seconds is recorded once.

We answer 200 for every one of these outcomes, including dropped events, so the provider does not retry. The body says what happened:

200 OK (recorded)
{
  "ok": true,
  "ingested": true,
  "decision": "GRANT",
  "userId": null,
  "zoneId": null
}

userId and zoneId are null when the email matched no user or the door is not mapped. A dropped delivery is { "ok": true, "ignored": "<reason>" } with one of these reasons:

  • unknown integration — no integration has that ID
  • integration disabled — the integration is paused
  • missing signature — no signature header, no raw body, or the integration has no secret
  • unparseable or non-access event — a wrong signature, or an event type or shape Frontelio does not recognise
  • replay-window dedup — the same event for the same door was just recorded

Verify the wiring

There is no test-webhook button. The Test button on an integration row only checks the stored credentials against the provider. To check the receiver, make a real tap on a synced door, or send a signed delivery yourself. This example uses the Kisi payload shape; for another provider, send that provider's shape (see its events section) and use its signature header.

bash
SECRET='<the webhook secret shown when you created the integration>'
URL='https://api.frontelio.com/api/v1/access/webhooks/<integrationId>'
BODY='{"event_id":"test-001","type":"ble.access","user":{"email":"worker@example.com","name":"Test Worker"},"lock":{"id":101,"name":"Front door"},"occurred_at":"2026-09-20T10:00:00Z"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -sS -X POST "$URL" -H 'Content-Type: application/json' -H "X-Kisi-Signature: $SIG" --data-binary "$BODY"

A recorded delivery answers {"ok":true,"ingested":true,"decision":"GRANT","userId":null,"zoneId":null}. userId is null because worker@example.com is not a user in your tenant; use a real staff email to see the row linked to them. zoneId is set once door 101 is a synced door mapped to a Zone. Then open /admin/access → Audit: the row has source EXTERNAL_KISI. Use a new event_id for each attempt. To see a drop, leave the header out or change one character of the secret: the answer is {"ok":true,"ignored":"missing signature"} or unparseable or non-access event, and nothing is recorded.

If a real tap never appears while the signed test does, the provider is not sending a signature Frontelio can verify, or it is sending an event type or payload shape Frontelio does not read. Compare its delivery log with the events section for that provider.

Next steps

  • For greenfield deployments without existing readers, see the Reader bridge deployment guide.
  • For wiring access events into your own systems, read them from GET /access/audit or /admin/access → Audit. Outbound webhook subscriptions (/settings/developer) cover a fixed list of events, and access events are not among them today. They are also separate from these inbound integrations.
  • For everything else access-related, the /access API reference is the source of truth for the runtime endpoints.