ProductConsole

Cabana Verify docs

Verify people by link, by a button on your site, or through the API. The result is a Trust Score from 0 to 100 with reasons. The document and face photos stay with Cabana Verify, and can be requested in a real need.

Quick start

  1. Sign in to the console with your work email. You get a code by email; there is no password.
  2. Add your website or app, and prove you own it. Test checks work before this; live checks need it.
  3. Make a test link in New link, open it on your phone, and finish it, or simulate the result.
  4. Add a webhook so your server hears about results.
  5. Go live with a link, the button, or the API.

Base URL https://cabana.africa/api/verify/v1. Every request is HTTPS and JSON.

Prove you own your website or app

So people know who is asking for their identity, and so no one can run checks in your name, live checks only run for websites and apps you have proven are yours. In the console, add the property and use any one of these with the token it shows you:

Websites

MethodWhat to add
DNS recordA TXT record on your domain (or on _cabana.yourdomain.com): cabana-site-verification=<token>
Meta tagOn your home page, inside <head>: <meta name="cabana-site-verification" content="<token>">
FileA file at https://yourdomain.com/<token>.txt containing the token

Apps

Verify your developer website first. Then publish this file on it, and make sure your Google Play or App Store listing links to that website:

// https://yourdomain.com/.well-known/cabana-apps.json
{ "apps": [ { "platform": "android", "id": "com.example.app", "token": "<token>" } ] }

If a store page cannot be read automatically, a person confirms it, usually the same day.

The daily look

Every day we read your home page again, check that your ownership record is still there, and compare it with what you registered. You get a property.updated webhook when its standing changes. If the record disappears you have seven days to restore it. If the site turns into something Cabana Verify cannot be used with (for example phishing, fake documents or a different business), checks pause until a person looks.

API keys

KeyWhereCan
cv_sk_live_…
cv_sk_test_…
Your server onlyEverything: create, read, list, cancel and simulate checks
cv_pk_live_…
cv_pk_test_…
Your web pagesOnly start a check, and only from your verified website’s own address

Test keys run the same flow, are never billed, and can be simulated. We store only a fingerprint of each key, so a lost secret key cannot be shown again: create a new one and revoke the old one in API keys.

Website button

Two lines on your verified website. The key is publishable: it only works from your verified site, so it is safe in the page.

<script async src="https://cabana.africa/cabana-verify/button.js" data-key="cv_pk_live_…"></script>
<div data-cabana-verify data-reference="user-123"></div>
AttributeMeaning
data-referenceYour ID for this person. Returned with the result and in webhooks.
data-labelThe button text. Default “Verify my identity”.
data-theme="light"A white button instead of indigo.
data-meta-*Extra fields kept with the check, e.g. data-meta-plan="gold" (up to 20).
data-return-urlWhere to come back to, on your verified site. Default: this page.
data-target="tab"Open the check in a new tab.
data-note="off"Hide the “Secured by Cabana Verify” line.

Any element of your own can start a check too: <button data-cabana-verify-start data-reference="user-123">. Or from your code:

CabanaVerifyButton.start({ reference: 'user-123', metadata: { plan: 'gold' } });
CabanaVerifyButton.on('error', e => console.warn(e.message));

Coming back

When the person finishes, they return to your page with ?cabana_verification=<id>&status=<status>, and the page receives an event:

document.addEventListener('cabana-verify:return', e => {
  // e.detail = { id, status }. A hint for the screen, nothing more.
  fetch('/my-server/check-verification?id=' + e.detail.id);
});

Never trust the status in the address. Anyone can type it. Confirm on your server with GET /sessions/<id> and your secret key, or act on the signed webhook.

Authentication

Send your secret key in the Authorization header (or X-Api-Key). Never put a secret key in a web page or an app.

curl https://cabana.africa/api/verify/v1/sessions \
  -H "Authorization: Bearer cv_sk_test_…"

Create a check

POST/sessions

Creates a check and returns its private link. Send the person to url.

FieldTypeMeaning
referencestring, ≤120Your ID for this person or case
redirect_urlhttps URLWhere to send them after, on a website you have verified. We add cabana_verification and status.
expires_in_days1–30How long the link works. Default 7.
metadataobjectUp to 20 string fields, kept with the check and returned to you
curl -X POST https://cabana.africa/api/verify/v1/sessions \
  -H "Authorization: Bearer cv_sk_test_…" -H "Content-Type: application/json" \
  -d '{"reference":"driver-2214","redirect_url":"https://yourdomain.com/onboarding/done"}'

The response is a verification object with "status": "created", plus url. Live checks need a verified website or app on your account.

Get a check

GET/sessions/{id}
curl https://cabana.africa/api/verify/v1/sessions/7f3c…-…-… -H "Authorization: Bearer cv_sk_live_…"

List checks

GET/sessions?reference=driver-2214&limit=25

Newest first, for the key’s environment. reference filters to one of yours; limit is up to 100.

Cancel a check

POST/sessions/{id}/cancel

Stops a check that has not finished. Its link stops working at once.

Sandbox: simulate a result

POST/sessions/{id}/simulate

Test checks only. Finishes the check with the outcome you choose, with example details, and sends your webhooks, so you can build your handling without a camera.

curl -X POST https://cabana.africa/api/verify/v1/sessions/{id}/simulate \
  -H "Authorization: Bearer cv_sk_test_…" -H "Content-Type: application/json" -d '{"outcome":"review"}'
# outcome: approved (score 94) · review (61) · declined (18)

The verification object

{
  "id": "7f3c2a90-…",
  "object": "verification",
  "env": "live",
  "reference": "driver-2214",
  "metadata": { "plan": "gold" },
  "status": "completed",              // created · opened · retry · processing · completed · cancelled · expired
  "decision": "approved",             // approved · review · declined (when completed)
  "score": 94,
  "band": { "id": "excellent", "label": "Excellent" },
  "reasons": [ { "code": "all_checks_passed", "text": "Every check passed." } ],
  "details": {                        // see “Details by tier”
    "full_name": "Amani Wanjiru", "first_name": "Amani", "last_name": "Wanjiru", "gender": "female",
    "document_type": "national_id", "issuing_country": "KEN", "is_adult": true, "document_valid": true, "source": "mrz"
  },
  "document": { "type": "id_card", "country": "KEN" },
  "under_review": false,
  "created_at": "2026-10-15T08:02:11Z", "completed_at": "2026-10-15T08:04:40Z", "expires_at": "2026-10-22T08:02:11Z",
  "links": { "result": "https://cabana.africa/api/verify/v1/sessions/7f3c2a90-…" }
}

A check with "decision": "review" is with a Cabana Verify specialist. When they decide, the decision and score change and you receive verification.updated.

Trust Score

Every check is folded into a score from 0 to 100: the document and its machine lines, whether the printed details agree, signs of a copy, a screen or editing, the face match, liveness, image quality, and fraud signals from across the network. Hard facts cap the score, so an expired document, an under-age holder, a failed liveness check or a reused document can never score highly.

BandScoreWhat it meansUsual action
Excellent90–100Every check strongApprove automatically
Accurate75–89Verified, with minor notesApprove
Average55–74Looks genuine; something to weighYour policy decides
Needs further verification35–54A person should lookOur specialists review it
High risk0–34A hard fact failed, or signs of fraudDecline

decision is Cabana Verify’s own recommendation. You can use the score to set a stricter line of your own, for example approving only from 85.

Details by tier

You receive only what your tier allows. No tier includes the document photo, the face, or the raw machine-readable lines in results, webhooks or the API. If you have a real need for the photos of a check, see Documents on request.

FieldBasicAssured
full_name, first_name, last_name✓✓
gender (female · male · unspecified)✓✓
document_type, issuing_country✓✓
is_adult (18 or over), document_valid✓✓
source (mrz: proven by check digits · docai: read by AI)✓✓
date_of_birth, document_number, expiry_date, issue_date, nationality, personal_number–✓

Assured is for businesses that need those details by law or licence, such as payments, lending, car hire and age-restricted goods. Ask for it in the console under Data access; we check your registration against the public register, usually within two working days.

Documents on request

The photos are not sent to you with results. If you have a real need, such as a dispute, a regulator’s or court’s request, or a suspected fraud, you can ask for the photos of a specific check.

  • Any business: write to verify@cabana.africa with the check’s reference and the reason. A specialist decides case by case.
  • Assured businesses: open the check in your console and press Request document photos, with a genuine reason. A Cabana Verify specialist reviews every request and approves it, declines it, or asks for more.
  • If approved: the document and face photos of that check are emailed once, from our system, to the owner of your workspace, and the release is recorded. We never send them to a different address.
  • Limits: photos can be requested only while we still hold them (up to a year after the check) and only for live checks.

What you receive is personal data and becomes your responsibility: use it only for the reason you gave, keep it secure, share it only where the law requires or allows, and delete it when you are done. See the terms.

Why you can rely on a result

You do not see the photos, so here is what stands behind every Trust Score:

  • Layered checks. The document and its machine-readable lines (proven by check digits); a face match on our own models; four seconds of light-based liveness; a second opinion from AI services (Groq and Cloudflare Workers AI) on the selfie against the document photo and a read of the document; image quality; and fraud signals from across businesses. A weak layer cannot carry a check on its own, and hard facts cap the score.
  • Independent disagreement matters. If the AI second opinion does not see the same person, or sees signs the selfie may not be live, the check can no longer be approved automatically and goes to a specialist (reason codes ai_face_disagrees and ai_spoof_suspected).
  • People on hard cases. Unclear and high-risk checks are reviewed by Cabana Verify specialists, who can approve, decline and mark fraud. You get verification.updated when they decide.
  • Reasons and a timeline. Every result carries each reason in plain words with a stable code, and the console shows what happened and when.
  • Records. Each time a specialist opens a photo, and each release to a business, is recorded. Photos are sealed on the person’s phone with a key made for the check, and the key is destroyed before the files are deleted.
  • Verified askers. Businesses prove they own their website or app before any live check, and we look at it daily.
  • Evidence on request. See Documents on request.

Reason codes

Each reason has a stable code for your logic and a plain text you can show your team.

CodeMeaning
all_checks_passedEvery check passed.
mrz_missingThe document’s machine-readable lines were not found.
mrz_unverifiedThe machine-readable lines did not pass their check digits.
no_mrz_documentThis document type has no machine-readable lines; its details were read by AI.
names_uncertainSome letters of the name were uncertain.
details_aiDetails were read by the AI document reader (not proven by check digits).
details_unreadableThe document’s details could not be read.
details_mismatchThe document’s printed details and machine lines disagree.
document_suspectThe document looks like a photo of a screen, a copy, or may be altered.
face_goodThe face matches the document photo well.
face_borderlineThe face match is borderline.
face_weakThe face does not clearly match the document photo.
face_mismatchThe face does not match the document photo.
continuityThe person changed during the check.
liveness_inconclusiveLiveness could not be confirmed.
liveness_failLiveness failed: this may not be a live person.
file_uploadThe document was uploaded from a file rather than photographed.
virtual_cameraA virtual camera was used.
attemptsSeveral attempts in a short time.
expiring_soonThe document expires within 30 days.
portrait_smallThe document photo is small.
qualityImage quality was low.
under_ageThe person is under 18 (proven by the document).
document_expiredThe document has expired (proven by the document).
under_age_unprovenThe person may be under 18.
expired_unprovenThe document may have expired.
document_reusedThis document was used before under a different identity.
identity_reusedThis face was verified before under a different name.
known_fraudMatches an identity marked as fraud.
layout_unusualThe document’s layout differs from genuine documents of this kind.
ai_face_disagreesA second, independent look at the selfie and the document photo did not see the same person.
ai_spoof_suspectedA second, independent look at the selfie saw signs it may not be a live person.
device_velocityMany checks from the same device recently.
returning_personVerified before with the same details.
reviewer_approvedApproved by a Cabana Verify specialist.
reviewer_declinedDeclined by a Cabana Verify specialist.
incompleteThe person did not finish the check.
cancelledThe check was cancelled.
expiredThe link expired before the check was finished.

Webhooks

Add an endpoint in the console under Webhooks. We POST JSON to it, and retry after 1, 5 and 30 minutes, then 2, 6 and 24 hours, until it answers with a 2xx.

EventWhen
verification.completedA check finished: approved, review or declined
verification.updatedA specialist decided a check that was in review
verification.cancelledThe person chose not to verify, or you cancelled it
property.verifiedOne of your websites or apps was verified
property.updatedIts standing changed in the daily look (for example paused, or ownership record missing)
pingYou pressed “Send a test”
{ "id": "evt_…", "type": "verification.completed", "created": 1760515480, "product": "verify",
  "data": { /* the verification object */ } }

Check the signature

Each request has Cabana-Signature: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of t + "." + the raw body under your endpoint’s signing secret (reveal it in the console). Reject anything older than five minutes.

import { createHmac, timingSafeEqual } from 'node:crypto';

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

Use the raw body exactly as received, before any JSON parsing. Deliveries can arrive more than once; use the event id to ignore repeats.

Errors and limits

Errors are JSON: { "error": "A sentence you can show", "code": "machine_code" }.

StatusCommon codesMeaning
400redirect_not_verifiedSomething in the request is not right; the sentence says what
401invalid_keyMissing, wrong or revoked key
402trial_endedThe free trial ended
403wrong_key_kind, origin_not_verified, suspendedNot allowed with this key, from this site, or right now
404–No such check on your account
409no_verified_propertyLive checks need a verified website or app
429quota_exceededThis month’s limit on your plan, or too many requests

The free trial includes 100 live checks and 1,000 test checks a month for 30 days. The button can start up to 10 checks a minute from one address.

The person’s side

What the person you send sees, so you can tell them what to expect:

  1. Your business name, what you will receive, and one consent box. It covers the check, sharing the result with you, and Cabana Verify keeping the check to improve its fraud detection. There is no separate tick box for that: it is part of how the service works, and the privacy sheet explains it in plain words.
  2. Their document. The camera captures it automatically when it is sharp. Passports, national IDs, residence permits and driving licences from any country.
  3. About four seconds looking at the screen while it changes colour. No head-turning. The face check needs a live camera; it cannot be uploaded.
  4. The result in seconds, then back to your page if you chose one.

They are told plainly that their photos stay with Cabana Verify and are not given to you unless a real need arises, in which case a specialist reviews the request first.