lumex.id عربي Get started

Developers

API reference

Version 1 · Base URL https://api.lumex.id/v1

Quick start

  1. Create a verification from your server for one of your users.
  2. Send the user to the url in the response. They scan their ID card or passport and take a short selfie in their phone browser.
  3. We post the result to your webhook and send the user back to your redirect_url.

All requests and responses are JSON. Call the API from your server only, never from a browser or mobile app, because your secret key must stay private.

Authentication

Send your secret key in the Authorization header. You get two keys in your dashboard:

KeyUse
lx_test_…Test mode. Runs the full flow with a test banner. Not billed.
lx_live_…Live mode. Real checks, billed per completed check.
Authorization: Bearer lx_live_…

Create a verification

POST/v1/verifications
FieldTypeDescription
referencestring, requiredYour own ID for the user, up to 100 characters. Returned in every response and webhook.
documentstringid_card, passport or any. Default any lets the user choose.
localestringen or ar. Default en. The user can switch language in the flow.
redirect_urlstringHTTPS address the user returns to when they finish. We add ?verification=vrf_… to it.
metadataobjectUp to 20 string keys and values of your own. Returned as sent.

Send an Idempotency-Key header with a unique value to make retries safe. The same key within 24 hours returns the first result instead of creating a second verification.

curl https://api.lumex.id/v1/verifications \
  -H "Authorization: Bearer lx_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2b7e-signup-1042" \
  -d '{
    "reference": "user_1042",
    "document": "any",
    "locale": "ar",
    "redirect_url": "https://your-site.com/verified"
  }'

Response 201 Created:

{
  "id": "vrf_7KQ2M9",
  "reference": "user_1042",
  "status": "created",
  "url": "https://verify.lumex.id/v/…",
  "expires_at": "2026-10-09T08:00:00Z",
  "created_at": "2026-10-08T08:00:00Z"
}

The link works for 24 hours and for one person only. Create a new verification if it expires.

Get a verification

GET/v1/verifications/{id}

Returns the full verification object. Use it to double-check a webhook, or to poll if you cannot receive webhooks.

curl https://api.lumex.id/v1/verifications/vrf_7KQ2M9 \
  -H "Authorization: Bearer lx_live_…"

The verification object

{
  "id": "vrf_7KQ2M9",
  "reference": "user_1042",
  "status": "approved",
  "decline_reasons": [],
  "document": {
    "type": "id_card",
    "country": "KWT",
    "number": "287041501234",
    "surname": "ALSALEM",
    "given_names": "FAHAD",
    "nationality": "KWT",
    "birth_date": "1987-04-15",
    "sex": "M",
    "expiry_date": "2029-03-01"
  },
  "checks": {
    "code_lines": "passed",
    "expiry": "passed",
    "liveness": "passed",
    "face_match": { "result": "passed", "score": 0.97 }
  },
  "metadata": {},
  "created_at": "2026-10-08T08:00:00Z",
  "completed_at": "2026-10-08T08:01:04Z"
}

Status moves in one direction:

StatusMeaning
createdLink made, user has not opened it yet.
in_progressUser opened the link and is scanning.
approvedAll checks passed. Final.
declinedOne or more checks failed. See decline_reasons. Final.
expiredThe link was not finished within 24 hours. Final, not billed.

Decline reasons: document_unreadable, document_unsupported, code_lines_invalid, document_expired, liveness_failed, face_mismatch.

Country codes are the three-letter codes printed in the document's code lines. Document and selfie images are deleted once the result is decided, so they are never returned by the API.

Webhooks

Add your webhook address in the dashboard. We send a POST with a JSON body for each event:

EventWhen
verification.startedThe user opened the link.
verification.approvedThe verification was approved.
verification.declinedThe verification was declined.
verification.expiredThe link expired unfinished.
{
  "id": "evt_3HZ81L",
  "type": "verification.approved",
  "created_at": "2026-10-08T08:01:04Z",
  "data": { /* the verification object */ }
}

Reply with any 2xx status within 10 seconds. If you do not, we retry with growing gaps for up to 24 hours. The same event can arrive more than once, so ignore an id you have already handled.

Check the signature on every webhook. Each request has a Lumex-Signature header like t=1791446464,v1=5257a8…. Compute an HMAC-SHA256 of t + "." + raw body with your webhook secret and compare it to v1. Reject it if t is more than 5 minutes old.

import crypto from "node:crypto";

export function verifyLumex(rawBody, header, secret) {
  const p = Object.fromEntries(header.split(",").map(x => x.split("=")));
  const t = Number(p.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(`${t}.${rawBody}`).digest("hex");
  if (!p.v1 || p.v1.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}

Use the raw request body exactly as received. Parsing and re-encoding the JSON first changes the bytes and breaks the signature.

Errors

Errors return a JSON body with a stable code you can check in your code, and a readable message:

{ "error": { "code": "invalid_field", "message": "redirect_url must start with https://" } }
HTTPWhen
400A field is missing or not valid.
401The key is missing, wrong or revoked.
402Your membership is not active.
404No verification with that ID in this mode.
409The same Idempotency-Key was used with a different body.
429Too many requests. Wait for the seconds in Retry-After.
5xxOur side. Retry with the same Idempotency-Key.

Need a hand?

Tell us about your stack through the contact form and we will help you connect your first site.