Link Identity Platform · v1

Getting started

A working integration in about thirty minutes: get a token, register a scanner, run an enrollment, run a verification, and wire up webhooks for the asynchronous results. This guide intentionally skips edge cases — for the full surface, see the API reference.

What this is#

Link Identity is biometric identification at the edge. A tenant owns one or more palm scanners. Users enroll once on any scanner in the tenant, and any other scanner in that tenant can verify them in roughly a second. Enrollment and verification are asynchronous — you create a challenge, the scanner captures the biometric, and the platform delivers the result over a webhook.

The REST surface is small on purpose: one token endpoint, three challenge endpoints (devices, enrollment, verification), and webhook management. The rest of this page walks each of them in order.

Prerequisites#

You will need:

  • A tenant in the Link Identity console and an admin or operator login.
  • An OAuth client. Create one at Integrations → OAuth Clients → New client. Copy theclient_id and client_secret — the secret is shown once.
  • An HTTPS endpoint that can receive POST deliveries. The examples below use https://your-app.example.com/webhooks/identity — substitute your own.
  • A paired scanner if you intend to run a real enrollment. For the steps below you only need its hardware_id (returned at the device-registration step).

Authenticate#

Exchange the client credentials for a Bearer token. This is the only unauthenticated call in the API.

Request
curl -X POST https://identity-api.imlink.network/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "06a4dc18-b06c-4e1c-ad25-204d44bf0c71",
    "client_secret": "cs_live_xxx"
  }'
Response 200
{
  "success": true,
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Tokens live for expires_inseconds (one hour). There is no refresh token in the client-credentials flow — request a new one when the current is within ~60 seconds of expiry. Cache it in memory for the duration; don't fetch a new token per request.

Warning

client_secret is shown once at creation. If you lose it, rotate the client from the console. Rotating creates a new secret; tokens issued from the old secret remain valid until they expire naturally.

Register a device#

Register a scanner before it can pair. The response carries a one-time pairing_code you hand to the physical scanner during provisioning.

Request
curl -X POST https://identity-api.imlink.network/v1/devices \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "device_name": "Lobby Scanner A",
    "location": "Main Entrance - Floor 1"
  }'
Response 201
{
  "success": true,
  "data": {
    "hardware_id": "hw_a1b2c3d4e5",
    "device_name": "Lobby Scanner A",
    "location": "Main Entrance - Floor 1",
    "pairing_code": "PAR-7X92-KZQM",
    "status": "pending_pair"
  }
}

Keep the hardware_id. Every enrollment and verification call needs it. Status flips from pending_pair to paired once the physical scanner consumes the pairing code — that transition is reported via the device.paired webhook (see Listen for events).

Full contract: Register Device.

Run an enrollment#

Create an enrollment challenge for a user against a specific paired scanner. The challenge tells the scanner to capture the palm; the platform stores the biometric template against your user_id.

Request
curl -X POST https://identity-api.imlink.network/v1/enroll \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "hardware_id": "hw_a1b2c3d4e5",
    "palm_type": "right",
    "metadata": { "employee_id": "EMP-4421" }
  }'

A 201 means the challenge is pending, not that the user is enrolled. The biometric capture happens on the scanner over the next few seconds, and the completion result arrives over the user.enrolledwebhook. Don't poll — wait for the event.

Full contract: Create Enrollment Challenge.

Run a verification#

Verification is a 1:1 palm match against a previously enrolled user. Same asynchronous shape as enrollment — create a challenge, wait for the webhook.

Request
curl -X POST https://identity-api.imlink.network/v1/verify \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "hardware_id": "hw_a1b2c3d4e5",
    "palm_type": "right",
    "metadata": { "session_id": "sess_0099", "access_point": "turnstile-7" }
  }'

The result arrives over the user.verified webhook with result: match | no_match. The palm_type must match the palm the user enrolled with — passing the wrong side will resolve as a no_match.

Full contract: Create Verification Challenge.

Listen for events#

The three events you'll handle: device.paired, user.enrolled, and user.verified. Register your endpoint once; the platform delivers a JSON payload to it whenever a subscribed event fires.

Subscribe
curl -X POST https://identity-api.imlink.network/v1/webhooks \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Events",
    "events": ["device.paired", "user.enrolled", "user.verified"],
    "endpoint": "https://your-app.example.com/webhooks/identity",
    "api_key": "whsec_your_secret_key_here"
  }'

Sample delivery for a successful verification:

Delivery
POST https://your-app.example.com/webhooks/identity
Content-Type: application/json
X-API-Key: whsec_your_secret_key_here

{
  "event": "user.verified",
  "event_id": "evt_9f8e7d6c5b",
  "fired_at": "2026-05-20T14:32:11Z",
  "data": {
    "challenge_id": "ch_verify_1a2b3c4d",
    "user_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "hardware_id": "hw_a1b2c3d4e5",
    "palm_type": "right",
    "result": "match",
    "verified_at": "2026-05-20T14:32:10Z",
    "metadata": { "session_id": "sess_0099", "access_point": "turnstile-7" }
  }
}

Warning

Respond with 2xx within 10 seconds. Non-2xx and timeouts retry with exponential backoff; if your handler is slow, acknowledge fast and process the payload asynchronously on your side.

Full contract: Create Webhook, Payload Format.

Production checklist#

Before you ship the integration:

  1. Rotate client secrets on a schedule — quarterly is a reasonable cadence. Rotation issues a new secret without invalidating tokens already in flight; switch over, then revoke the old one. Treat a leaked secret as an incident — delete the OAuth client entirely to kill tokens immediately.
  2. Verify the X-API-Key header on every delivery before you trust the payload. Constant-time compare against the value you set during subscription — never just check for header presence.
  3. Acknowledge fast, process async — return 200 within a second or two, then enqueue the work. Anything slower invites retry storms during traffic spikes.
  4. Handle 401, 403, and 422 distinctly — 401 means the token is wrong or expired (refresh and retry once), 403 means the OAuth client lacks the scope for that endpoint (a permissions problem on your side), and 422 means the request body didn't validate. Lumping them together makes failures invisible.
  5. Test against a staging tenant first — create a parallel tenant in the console, point staging there, run the full enrollment → verification loop end-to-end before pointing production traffic at the real one.

Where to next#

You now have the shape of every call. Bookmark the API reference for the full parameter lists, status tables, and per-endpoint notes. SDKs in Node, Python, and Kotlin for the Android scanner builds are tracked on the SDKs page.