# ZetaID documentation Source: https://docs.zetaid.com/ ZetaID checks that a person is who they say they are: an identity document, a selfie or a hand-movement check, sanctions screening, a wallet proof. You start a check from your server, send the person to the ZetaID verification page, and receive the result on your server. ## How a verification works 1. **Your server creates a verification** with your API key (`POST /api/v1/verifications`). ZetaID answers with a `verification_id` and a `url`. 2. **The person opens the `url`**: the ZetaID verification page walks them through the steps your key asks for (for example: photograph the ID card, then a short selfie). On a computer without a camera, the page offers to continue on a phone. 3. **ZetaID checks what was captured** and decides: `verified`, `rejected` or `needs_manual_review`. 4. **Your server gets the result**: a signed webhook to the URL you set on the key, and on request with `GET /api/v1/verifications/{id}`. 5. **The person comes back** to your `return_url`, and your site continues with what your server now knows. Your API key never leaves your server. The person's browser only ever talks to the ZetaID verification page. - [Quickstart](https://docs.zetaid.com/quickstart/): Your first verification in five steps. - [Integrate a website](https://docs.zetaid.com/guides/website-integration/): A complete, safe integration, step by step. - [API reference](https://docs.zetaid.com/reference/api/): The two calls, their fields and answers. - [Agent skill](https://docs.zetaid.com/agents/agent-skill/): Let your coding agent build the integration. ## What you need **An operator console login** We set up your organization and send you the login for the operator console. There you create API keys, choose what each key checks, and see every verification. **A server** Any backend that can make HTTPS requests and receive one: Node.js, PHP, Python, Java, Go, … The API is not meant to be called from a browser or a mobile app. --- # Quickstart Source: https://docs.zetaid.com/quickstart/ 1. **Create an API key.** In the operator console, open **API keys** → **New API key**. Give it a name (for example `Shop – production`) and the URL where your server receives webhooks. The console shows the **API key** (`vk_live_…`) and the **webhook signing secret** (`whsec_…`) exactly once: store both in your server's secret store. 2. **Choose what the key checks.** In the same dialog, or later with **Edit key**, pick the steps of the flow, for example *document* then *liveness*. See [Configure a key](https://docs.zetaid.com/guides/configure-a-key/). 3. **Create a verification from your server.** ```sh curl -X POST https://verify.zetaid.com/api/v1/verifications \ -H "X-SDK-Key: $ZETAID_API_KEY" \ -H "Content-Type: application/json" \ -d '{"return_url": "https://shop.example/checkout/verified?order=A-17", "extra_data": {"order_id": "A-17"}}' ``` ```json { "verification_id": "3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64", "url": "https://verify.zetaid.com/?verification_id=3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64", "flow": ["document", "liveness"], "due_at": "2026-11-02T10:15:00Z", "expires_at": "2027-01-30T10:15:00Z" } ``` 4. **Send the person to `url`.** Redirect their browser to it. When they are done, the page sends them back to your `return_url`. 5. **Receive the result.** ZetaID posts a `verification.completed` webhook to your key's URL. Check its signature, then act on `status`: ```json { "event": "verification.completed", "verification_id": "3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64", "status": "verified", "reason_code": null, "export": { "personal_info": { "first_name": "ERIKA", "last_name": "MUSTERMANN", "date_of_birth": "1983-08-12" } }, "extra_data": { "order_id": "A-17" }, "completed_at": "2026-11-01T10:19:42Z" } ``` You can also read the same fields at any time with `GET /api/v1/verifications/{verification_id}`. > **Server side only** The API key identifies your organization and is billed to it. Keep it on your server. A request to the API from a web page is refused (`403 SERVER_SIDE_ONLY`). Next: [integrate a website](https://docs.zetaid.com/guides/website-integration/) end to end, with signature checks, the return page and expired links. --- # API keys Source: https://docs.zetaid.com/guides/api-keys/ An API key does two things: it authenticates your server's requests, and it carries the settings of every verification created with it (which steps the person goes through, how long data is kept, where webhooks go). ## Create a key 1. Sign in to the **operator console** with your organization's main login (only main logins see **API keys**). 2. Open **API keys** → **New API key**. 3. Enter a **name** that says where the key is used, for example `Shop – production` or `Hotel check-in – staging`. 4. Enter the **webhook URL**: the HTTPS address on your server that receives results (you can add or change it later). 5. Choose the flow and the other settings ([Configure a key](https://docs.zetaid.com/guides/configure-a-key/)). 6. Create the key. The console shows two values **once**: - the **API key** (`vk_live_…`), sent with every API request; - the **webhook signing secret** (`whsec_…`), used to check that webhooks really come from ZetaID ([Get the result](https://docs.zetaid.com/guides/get-the-result/#check-the-signature)). Copy both into your server's secret store (environment variables, a vault, your platform's secret manager). ZetaID keeps only a fingerprint of the API key and cannot show it again; if it is lost, create a new key. ## Use a key Send the key in the `X-SDK-Key` header of every API request: ```http POST /api/v1/verifications HTTP/1.1 Host: verify.zetaid.com X-SDK-Key: vk_live_8c1f… Content-Type: application/json ``` A missing, wrong or deactivated key gets `401 SDK_KEY_INVALID`. > **Never in a browser or an app** Anyone who has the key can create verifications billed to your organization and read results. Don't put it in JavaScript that runs in the browser, in a mobile app, or in a repository. The API refuses requests that come from a web page (`403 SERVER_SIDE_ONLY`). ## One key per use Create a separate key for every site, app or environment. Each key has its own flow, retention and webhook URL, and the operator console shows which key created each verification. A test environment gets its own key with its own webhook URL. ## Replace a key To replace a key (a suspected leak, a staff change): 1. Create a new key with the same settings. 2. Deploy it to your server. 3. In **API keys**, deactivate the old key. Verifications that are already running continue; new requests with the old key are refused. A key that never created a verification can be deleted; any other key can only be deactivated, so its verifications keep their history. ## The webhook signing secret Every key has its own signing secret. **Rotate signing secret** on the key replaces it and shows the new one once; from that moment ZetaID signs this key's webhooks (including retries of earlier ones) with the new secret, so update your server right away. Keys created before signing secrets existed show **Shared platform signature**: use **Create signing secret** on them to give them their own. --- # Configure a key Source: https://docs.zetaid.com/guides/configure-a-key/ Every verification takes its settings from the key that created it, as they were at that moment. You change them in the operator console: **API keys** → the key → **Edit key**. A change applies to verifications created afterwards; running ones keep what they started with. ## Flow (module order) The steps the person goes through, in this order. Click the modules in the order you want them; the number on each shows its position, and a second click removes it. An empty flow uses the default: `document`, then `liveness`. | Module | What the person does | What you get | |---|---|---| | `document` | Photographs a passport, ID card, residence permit or driving licence (both sides where needed). | Name, date of birth, nationality, document number and expiry, read from the document and cross-checked. | | `liveness` | Takes a short selfie. | Proof that a live person was present, matched against the document photo. | | `palm_liveness` | Moves an open hand to a few dots on the screen (Human check). | Proof that a live person, not a bot, did it — no face, no document. | | `terms` | Accepts your terms. | The acceptance, with time. | | `form_data` | Fills in a short form (for example an address). | The values, in the result's `contact_info`. | | `email` | Confirms an email address with a code. | The confirmed address. | | `phone` | Confirms a phone number with a code by SMS. | The confirmed number. | | `aml` | Nothing: runs automatically after the other steps. | Sanctions screening of the name read from the document. | | `nfc` | Holds their passport or ID card to their phone so the chip can be read. | The chip's data, authenticated by the issuing state. Available on request. | | `wallet` | Shares a proof from their digital identity wallet. | The requested attributes, for example *over 18*. Needs a registration with us first. | A flow needs at least one step the person does; `aml` alone is not a flow. The order matters: put `document` before `liveness` so the selfie can be matched to the document photo. You can also send a different `flow` with a single verification ([Create a verification](https://docs.zetaid.com/guides/create-a-verification/)); the key's flow is the default. ## Keep images for (days) How long the captured images (document photos, selfie) are kept. Default 90. After that they are deleted; the verification's outcome stays. ## Keep personal data for (days) How long the personal data the check produced (names, dates, numbers, the result's `export`) is kept. It must be at least as long as the images, and at most 3,650 days (10 years). New keys start with the same value as the images. This is your decision: keep what your own obligations require (for example a hotel's guest register) and no more. After the deadline the verification keeps only its outcome — `GET /api/v1/verifications/{id}` then answers with `status` and `export: null`. ## Due days How long the link (`url`) works before the verification expires without a result. Default 1 day. Expired verifications send a [`verification.expired`](https://docs.zetaid.com/reference/webhooks/#verificationexpired) webhook. Once the person has started, a verification also expires after 24 hours without any input (reason `ABANDONED`). To change that window, set `abandon_after_hours` in the key's raw config JSON (`0` turns it off). ## Policies Leave these empty to use the platform defaults; change them only when you know why. - **Blur threshold** (`quality_threshold_0_10`, default 4.5): how sharp a document photo must be. A lower value accepts softer photos. - **Face match distance** (`face_match_max_distance`, default 0.7): how close the selfie must be to the document photo. A lower value is stricter. - **Chip face match distance** (`chip_face_match_max_distance`, default 0.7): the same for the photo on a document's chip. - **Gates** (`recapture_gate`, `blackout_gate`, `liveness_spoof_gate`): what happens when a check finds a photographed screen, a covered document or a spoofed selfie. `flag_review` sends the verification to manual review; `shadow` only records the finding. ## Image redaction Hides parts of images (machine-readable zone and QR codes, the portrait, specific fields) when they are shown in the operator console. The stored images are not changed. ## Webhook URL Where ZetaID posts this key's [webhooks](https://docs.zetaid.com/reference/webhooks/). It must be HTTPS and answer within 20 seconds. Empty = no webhooks; read results with `GET /api/v1/verifications/{id}` instead. > **Note** Want only an *over 18* answer instead of the full identity, or results offered to the person's wallet? Those settings exist, but we set them up with you — contact us. --- # Create a verification Source: https://docs.zetaid.com/guides/create-a-verification/ Create one verification per person and occasion (an order, a booking, an account sign-up), from your server, at the moment the person needs it. ```http POST https://verify.zetaid.com/api/v1/verifications X-SDK-Key: vk_live_… Content-Type: application/json ``` ## The request Every field is optional; an empty body (`{}`) uses the key's settings. | Field | Type | | |---|---|---| | `return_url` | string | Where the verification page sends the person when they are done. | | `extra_data` | object | Your own reference, returned in every webhook and `GET` (for example `{"order_id": "A-17"}`). | | `flow` | string[] | The steps for this verification only, instead of the key's flow. Module names as in [Configure a key](https://docs.zetaid.com/guides/configure-a-key/#flow-module-order). | | `module_overrides` | object | Settings for single modules of this verification, for example prefilled form values (below). | **`return_url`**: the page shows "Continue" and moves on after a few seconds. ZetaID adds nothing to the URL, so put your own reference in it (`https://shop.example/checkout/verified?order=A-17`). Treat the return as "the person is back", never as "the person is verified": the result comes from your server ([Get the result](https://docs.zetaid.com/guides/get-the-result/)). **`extra_data`**: a small JSON object of your own identifiers. Don't put personal data in it (no names, birth dates or email addresses) — it is stored with the verification and sent back in every webhook. **`module_overrides`**: for a `form_data` step, prefill values and lock the ones the person must not change: ```json { "module_overrides": { "form_data": { "values": { "booking_reference": "ABC123" }, "locked": ["booking_reference"] } } } ``` ## The answer `201 Created`: ```json { "verification_id": "3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64", "url": "https://verify.zetaid.com/?verification_id=3f6c2a9e-7d41-4b8a-9c2e-5a1f0d7b3e64", "flow": ["document", "liveness"], "due_at": "2026-11-02T10:15:00Z", "expires_at": "2027-01-30T10:15:00Z" } ``` | Field | | |---|---| | `verification_id` | Store it with your order, booking or account. | | `url` | The link for the person. Use it as it is; it can be on your own branded host if we set one up for you. | | `flow` | The steps this verification will go through. | | `due_at` | Until when the link works. Afterwards the verification expires (`verification.expired`). | | `expires_at` | When the captured images will be deleted (the key's image retention). | ## Examples **Node.js** ```js const res = await fetch("https://verify.zetaid.com/api/v1/verifications", { method: "POST", headers: { "X-SDK-Key": process.env.ZETAID_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ return_url: `https://shop.example/checkout/verified?order=${order.id}`, extra_data: { order_id: order.id }, }), }); if (!res.ok) throw new Error(`ZetaID ${res.status}: ${(await res.json()).error?.code}`); const { verification_id, url } = await res.json(); ``` **Python** ```python res = requests.post( "https://verify.zetaid.com/api/v1/verifications", headers={"X-SDK-Key": os.environ["ZETAID_API_KEY"]}, json={ "return_url": f"https://shop.example/checkout/verified?order={order.id}", "extra_data": {"order_id": order.id}, }, timeout=20, ) res.raise_for_status() verification = res.json() # verification["verification_id"], verification["url"] ``` **PHP** ```php $ch = curl_init('https://verify.zetaid.com/api/v1/verifications'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => ['X-SDK-Key: ' . getenv('ZETAID_API_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'return_url' => 'https://shop.example/checkout/verified?order=' . urlencode($order->id), 'extra_data' => ['order_id' => $order->id], ]), ]); $verification = json_decode(curl_exec($ch), true); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 201 on success ``` ## When it fails | Status | `error.code` | What to do | |---|---|---| | 401 | `SDK_KEY_INVALID` | The key is missing, wrong or deactivated. | | 402 | `CREDITS_EXHAUSTED` | Your prepaid balance is below zero. Top up in the operator console (**Billing**). | | 403 | `SERVER_SIDE_ONLY` | The request came from a web page. Call the API from your server. | | 422 | `FLOW_INVALID` | An unknown module in `flow`, or no step the person does. | | 503 | `SYSTEM_TEMPORARILY_UNAVAILABLE` | Checks that analyse images can't start right now. Retry after a short wait. | All errors: [Errors](https://docs.zetaid.com/reference/errors/). > **Note** Create the verification when the person is about to start it, not in advance: the link has a deadline (`due_at`). If it expires, create a new verification. --- # Send the person to the verification page Source: https://docs.zetaid.com/guides/send-the-user/ The `url` from [Create a verification](https://docs.zetaid.com/guides/create-a-verification/) opens the ZetaID verification page. It asks for the camera, guides the person through the steps of the flow, and shows them how it went. ## On a website Redirect the browser to `url` in the same tab: ```js res.redirect(303, verification.url); ``` or show a button that links to it. Opening it in a new tab works too; the person then returns in that tab. **On a computer**, the page offers to continue on a phone: it shows a QR code (and can email the link) so the person can photograph the document with the phone's camera. Your site doesn't need to do anything for that. **In a shop or at a front desk**, you can show `url` as a QR code that the customer scans with their own phone. > **No iframes** Don't embed the page in an `