Verification Suite
Set up a flow in your dashboard, choose the checks, and give each applicant a link. They complete the steps on their phone without an account. You are billed per completed check from your wallet; applicants never pay. You can use it with no code (WhatsApp links), put it on your site with an iframe, or drive it from your server with the API.
How it works
- Open Verification Suite and create a flow. The wizard shows the cost per applicant as you choose.
- Share it: a personal link per applicant (best for WhatsApp), or one public link (best for a website).
- The applicant agrees to your consent text, enters their details and finishes each step. If they close the page, the same link resumes.
- You get an email, a message in the dashboard and mobile app, and a webhook. Open the applicant to see the results and approve or reject.
The checks, and what each one shows
| phone_otp | Phone number check (SMS code) | The applicant can receive messages on this number now. Not whose number it is. |
| email_otp | Email check | The applicant can read mail sent to this address. |
| id_number | NIN or BVN (Nigeria only) | The number exists and the name on the record matches. Not that the person typing it owns it. The NIN lookup returns no photo. |
| id_document | ID card photo read (OCR) | Text read from the card and compared with the name. Not a forgery check. |
| face_match | Selfie matched to ID card photo | The selfie looks like the photo on the card the applicant uploaded. Not that the card is genuine. |
| liveness | Liveness test | A live face was in front of the camera, not a photo or a screen. Not who it is. |
| address | Address check | The address exists on the map, whether the phone was near it, and whether a bill mentions it. Presence is not residence. |
| bank_account | Bank account name check (Nigeria only) | The account exists and the name is compared. Not that the applicant controls it. |
| bank_statement | Bank statement upload | Stored for you to read. Not machine-checked. |
| cac | Company registration (CAC, Nigeria only) | The registration number exists and the name is compared. |
| business_registry | Company registry | The company exists in an official register (UK, Norway, Czechia, France, US SEC filers). |
| vat | VAT or tax number | Valid in a live official check where one exists; otherwise a format check, labelled as such. |
Each check is priced like the same check on its own (see pricing). A check that could not run is refunded, and a retry never charges twice. Nigeria-only checks are hidden for other countries.
Embed on your website
Add the websites that may show the form under Allowed websites in the flow settings. Then paste an iframe:
<iframe
src="https://identifyorg.com/v/vf_YOUR_FLOW_ID?embed=1"
title="Identity verification"
allow="camera; geolocation"
style="width:100%;max-width:480px;height:760px;border:0"
></iframe>The iframe must have allow="camera; geolocation", or the browser will not let the form use the camera or the address step. Or use the script, which adds the iframe, resizes it and raises events on your element:
<div id="identifyorg-verify" data-flow="vf_YOUR_FLOW_ID"></div>
<script src="https://identifyorg.com/embed/verify.js" async></script>
<script>
var el = document.getElementById("identifyorg-verify");
el.addEventListener("identifyorg:completed", function (e) {
// e.detail = { source: "identifyorg-verify", type: "completed", reference: "..." }
window.location = "/thanks";
});
</script>For a flow with personal links, create a session on your server and pass its token: data-session="TOKEN" instead of data-flow.
Events your page receives
The form sends postMessage events to the page that holds it. Always check the origin:
window.addEventListener("message", function (e) {
if (e.origin !== "https://identifyorg.com") return; // always check the origin
var d = e.data;
if (!d || d.source !== "identifyorg-verify") return;
if (d.type === "resize") iframe.style.height = d.height + "px";
if (d.type === "completed") showThanks();
});| ready | The page loaded. `is_test` says whether it is a rehearsal. |
| started | The applicant agreed and entered their details. |
| step | A new step is showing: `{ step, index, total }`. |
| received | A step was kept for processing because your wallet or a limit could not cover it right now. The applicant can carry on. |
| resize | The height the page needs: `{ height }` in pixels. The script resizes the iframe for you. |
| completed | The applicant submitted. Carries your `reference` when you set one. This is not the result: read the result with the API or a webhook. |
| error | The link could not be opened (expired, not valid, not allowed on this site). |
Every message has source: "identifyorg-verify" and a type. The script also fires identifyorg:<type> DOM events on your element, and identifyorg:complete when the applicant submits. Do not treat completed as proof of identity: it only means they finished. Read the result from your server.
From your server (API)
Create a session with a secret key, send the applicant the url, then read the result. A publishable key cannot do this. See POST /v1/verification-sessions and GET /v1/verification-sessions/{id}.
curl -X POST https://api.identifyorg.com/v1/verification-sessions \
-H "X-IdentifyOrg-Key: io_live_..." \
-H "Content-Type: application/json" \
-d '{
"flow_id": "vf_YOUR_FLOW_ID",
"reference": "order-1042",
"applicant": { "name": "Ada Obi", "phone": "08031234567" }
}'
# => { "id": "vs_...", "url": "https://identifyorg.com/v/Qk3x...", "status": "created", ... }Webhooks: verification_session.started, .completed, .failed and .awaiting_funds. See Webhooks for signing.
{
"event": "verification_session.completed",
"data": {
"id": "vs_3f9a1c...", "reference": "order-1042", "status": "verified",
"applicant": { "name": "Ada Obi", "phone": "08031234567", "email": null },
"id_number": "*******8901", "cost": 260.0, "currency": "NGN",
"steps": [ { "step": "id_number", "status": "passed" }, { "step": "face_match", "status": "passed" }, { "step": "liveness", "status": "passed" } ]
}
}Who pays, and what if the wallet is empty
- The link identifies the flow, and the flow identifies your business on our server. The browser never says who pays, and the flow id in an embed is public (like a publishable key); it cannot be used to read anything.
- Each step is charged once, only when the check completed. If we could not complete it, it is refunded and the applicant can try again.
- If your wallet (or a daily limit you chose to set) cannot cover a step, the applicant's data is kept, they are told it was received, the session shows
awaiting_funds(oron_holdfor a limit), you are emailed a link to fund your wallet, and the checks finish automatically after you top up or the limit resets. - Test mode and test keys never charge and never call a real provider.
Security of the embedded form
- By default the form cannot be framed by any website except ours (for the dashboard preview). Each flow lists the websites allowed to frame it, and the page is served with a
Content-Security-Policy: frame-ancestorsheader built from that list. We also check the referring page of a framed load, and the page tells our server which site holds it when it starts. - Public links put the cheap phone check first, limit how many applicants one connection can start a day, include a hidden bot trap, and email you once a day when your spend reaches an alert level you set. Nothing is limited by default; you can optionally set a hard daily limit per flow.
- Session tokens are long and random. Links expire (you choose when). Uploads are checked by content and size; photos are re-saved without hidden metadata such as GPS.
- ID numbers are encrypted. Pictures and documents are kept privately and are shown only to your signed-in account through addresses that last two minutes. Showing a full ID number, opening evidence, exporting and deleting are written to an audit log.
- Consent is recorded with the time and the version and a fingerprint of the exact wording the applicant agreed to.
- Data is deleted after the retention period you choose, and you can delete an applicant's data at any time.
Framing limits stop other websites from showing your form. They cannot stop a person from opening your public link directly, which is why the per-connection limits and the usage alert exist.
Finding this hard to integrate?
Open a ticket and our technical team will help you get it working. We reply by email. Or WhatsApp or call +234 913 995 7618.

