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_idandclient_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.
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"
}'{
"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.
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"
}'{
"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.
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.
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.
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:
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
Full contract: Create Webhook, Payload Format.
Production checklist#
Before you ship the integration:
- 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.
- 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.
- Acknowledge fast, process async — return 200 within a second or two, then enqueue the work. Anything slower invites retry storms during traffic spikes.
- 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.
- 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.