Skip to main content

Web Integration (JavaScript)

There is no Identiwise JavaScript SDK and no npm package. Nothing to install, nothing to keep up to date. Getting a person through a verification in a browser is done in one of two ways:

  1. Hand the subject to the hosted verification UI — a mobile-friendly page served from your own tenant host that does camera capture, the details form, re-uploads and messaging for you. This is the recommended path and the one most integrations use.
  2. Build your own UI against the documented X-Api-Key routes — see §4 for the one constraint that matters today.

Either way, the credential comes from your backend: mint a short-lived, capability-scoped ephemeral token with your bearer token, then give that ephemeral token — and only that — to the browser.


1. Hand off to the hosted UI

Mint the token server-side, then redirect the subject (or open the page in an iframe):

https://acme.identiwise.com/verify/?token=EPHEMERAL_TOKEN
// Your server returns { token: "..." } from POST /api/v1/ephemeral-tokens
const { token } = await fetch("/my-backend/start-verification", { method: "POST" })
.then((r) => r.json());

window.location.href =
`https://acme.identiwise.com/verify/?token=${encodeURIComponent(token)}`;

Two browser rules apply because the page uses the camera: it must be served over HTTPS (it is), and if you embed it in an iframe you must grant the permission explicitly:

<iframe
src="https://acme.identiwise.com/verify/?token=EPHEMERAL_TOKEN"
allow="camera"
style="width:100%;height:720px;border:0"
></iframe>

Redirecting is the simpler option: it avoids permission-delegation differences between browsers and gives the subject the full screen on a phone.


2. What the hosted UI does

Two modes

  • Workflow wizard. If the token is bound to a workflow_id, the UI reads the workflow's ordered steps and walks the subject through them one at a time: verify_license (capture the document), check_selfie (capture a face), age_gender_estimation (run the estimate). A step type the page does not recognise renders a generic instruction panel rather than failing.
  • Dashboard tiles. With no workflow attached, the page shows a tile board — Details, Capture, Re-upload, Messages — and hides the tiles the token does not permit.

Capture

The page lists the device's cameras and lets the subject switch between them, then captures a still frame and submits it. If the subject is at a desktop with no usable camera, Show QR Fallback renders a QR code that carries the same session to their phone — they finish there, and no separate login is involved.

Details, re-uploads, messages

  • Details — shows the subject's stored details and, when the token carries can_edit_details, lets the subject correct them before submitting. This matters: the OCR step scores the text read off the document against these values.
  • Re-upload — when an administrator rejects a submission and asks for a new one, the page lists exactly the items that need replacing.
  • Messages — the subject can read an administrator's note and reply.

Result feedback

Each capture is judged synchronously and the page reacts to the response: approved, parked for review with the stated reason, or an explicit error. A submission parked because an AI service was unavailable is never presented as a decision about the person.


3. The capability endpoint

The hosted UI configures itself by calling, with the subject's credential:

GET /api/v1/ephemeral/capability
Header: X-Api-Key: EPHEMERAL_TOKEN

The response describes what this token may do — the useful fields being upload_type, can_edit_details, view_subject_details, can_list_reupload, can_list_need_reply, min_age, require_face_match, the attempt counters (max_uses / use_count, max_license_attempts / license_use_count, max_selfie_attempts / selfie_use_count) and workflow_steps. If the token was minted without can_return_capabilities, the endpoint answers {"can_return_capabilities": false} and nothing else.

Not everything echoed is enforced

The response also returns a few settings that are stored with the token but not yet applied by the pipeline — the two *_threshold values among them. Treat those as configuration you saved, not as behaviour you can rely on. Every field listed in the paragraph above is enforced.


4. Building your own UI

The subject routes are plain HTTP and you may drive them yourself:

RoutePurpose
GET /api/v1/ephemeral/capabilityWhat this token may do.
GET / POST /api/v1/subject/detailsRead, and (with can_edit_details) correct the subject's details.
POST /api/v1/subject/upload?type=license|selfie|analyze|verify_ageSubmit an image.
POST /api/v1/subject/reupload?type=license|selfie&id={submission_id}Replace a rejected submission.
GET / POST /api/v1/subject/messagesRead and post correspondence.
POST /api/v1/subject/request-deletionLodge an erasure request (requires confirmation: true).
Where your page may run

The API does not emit cross-origin resource-sharing headers yet, so a browser page served from a different origin cannot call these routes directly. Two options work today: serve your UI from the tenant host, or proxy the calls through your own backend, which also keeps the ephemeral token off the page. A CORS allow-list exists in the account portal but is stored rather than enforced — see Platform Management.

A minimal upload, for a page that is allowed to call the API directly:

async function uploadDocument(file, verificationId) {
const form = new FormData();
form.append("image_file", file); // the field name is exactly image_file
form.append("verification_id", verificationId); // your own id for this verification

const res = await fetch(
"https://acme.identiwise.com/api/v1/subject/upload?type=license",
{ method: "POST", headers: { "X-Api-Key": EPHEMERAL_TOKEN }, body: form }
// do not set Content-Type — the browser adds the multipart boundary
);
return { httpStatus: res.status, body: await res.json() };
}

Notes that will save you a support round-trip:

  • verification_id is required for type=selfie, analyze and verify_age, and is how a retake continues the same submission instead of starting a new one.
  • An upload or re-upload consumes one token use; reads do not.
  • A 409 means the submission is already approved, or an administrator decided it while your upload was in flight. No submission is recorded, and any file already written is removed. The two cases differ on cost: a 409 raised before the upload starts consumes no token use, while one raised after an administrator decided the submission mid-upload has already spent one — budget your max_uses and attempt caps accordingly. Re-opening an approved submission is an administrator action.
  • A 503 on analyze / verify_age means the AI service could not be reached, so nothing is asserted about the person — the file was still stored and the token use still spent. Honour Retry-After.
  • POST /api/v1/subject/request-deletion exists for the subject's own erasure request; the hosted UI does not render a control for it today, so surface it from your own page if you need to offer one.

5. Reading the result

The upload response carries a status, and for a parked submission a human-readable reason. Branch on the status, and treat anything you do not recognise as "not decided yet" rather than as a failure:

const { body } = await uploadDocument(file, verificationId);

switch (body.status) {
case "LICENSE_APPROVED":
// OCR matched the details on file; body.score carries the score
break;
case "LICENSE_PENDING_APPROVAL":
// Parked for a human. body.reason says why — show it, do not treat it as a rejection
break;
case "ATTEMPT_SUPERSEDED":
// A newer attempt or an administrator's decision replaced this one; read the current state
break;
default:
// Show body.reason if present and let the subject continue
}

There are no webhooks and no callbacks. The verdict is in the HTTP response. To follow what happens afterwards — an administrator approving, rejecting or asking for a re-upload — poll GET /api/v1/admin/review or GET /api/v1/admin/submissions from your backend, with your bearer token; never from the browser.