Website Redirect
The website redirect is the lightest way to add identity verification to a web platform. Instead of embedding capture components, you send the user to the IDV Platform hosted verification app, they complete the flow there (selfie + identity document), and they are redirected back to your site with the result. Your backend receives the verified customer data through a callback.
The same hosted app can also be embedded as an iframe instead of a full-page redirect — the setup is nearly identical and both are covered below.
When to use it
- You want verification live quickly, with minimal front-end work.
- You don't need to embed capture inside your own branded UI (for that, use Web Components or the Mobile SDK).
- A browser-based journey is acceptable, with the hosted app handling capture, quality guidance, and processing.
Prerequisites
Verification runs against your registered configuration. You set most of it yourself in Integration Settings → Web redirect, per environment:
| Value | Purpose |
|---|---|
verifiedUrl | Where the user is redirected after a successful verification. |
rejectedUrl | Where the user is redirected when the verification is rejected. Optional — when empty, unverifiedUrl is used. |
unverifiedUrl | Where the user is redirected when they cancel, or when the verification cannot be completed for a technical reason. |
logoUrl | Your logo, shown in the hosted app (SVG for light background recommended; PNG supported). |
The redirect flow must also be enabled with the Enable web redirect toggle on the same tab. The sensitive customer data does not travel through the browser: it is POSTed to your backend as a webhook, to the endpoints you register on the Webhooks tab.
Per environment, your OAuth2 client credentials are generated on the API interface tab of Integration Settings, and the signing secret used to verify the callback is shown when you add a webhook endpoint. The endpoints themselves are fixed per deployment:
| Endpoint | Innovatrics-managed service (European SaaS) |
|---|---|
| Hosted verification app — where you send the user | https://client-app.idv.innovatrics.com |
| API service URL — where your backend creates the session | https://api.idv.innovatrics.com/bff |
| Token endpoint — where your backend gets the access token | https://keycloak.idv.innovatrics.com/realms/integrators/protocol/openid-connect/token |
| Dashboard — where you configure the above | https://platform.idv.innovatrics.com |
A self-hosted deployment uses your own hostnames with the same path structure.
The gateway routes each service on its own path lane, and the session endpoint lives behind /bff. Your SERVICE_URL must therefore end in /bff, so that ${SERVICE_URL}/api/v1/session resolves to https://api.idv.innovatrics.com/bff/api/v1/session. Using the bare hostname returns a 404 with nothing in the body to explain why.
Sandbox and Production share these URLs — the environment is selected by which credentials you authenticate with, not by a different host.
The OAuth2 client secret must never live in front-end code — public front-ends would expose it and allow your account to be abused. Your backend authenticates, obtains the token, and calls the API. The front-end only performs the redirect.
How it works
At a high level, the data flows like this:
- The user starts verification on your website.
- Your website asks your backend for a session token.
- Your backend obtains an access token (OAuth2 client-credentials) and calls the IDV Platform API to create a session.
- The API returns a
sessionToken; your backend passes it back to the website. - The website sends the user to the hosted verification app (redirect) or embeds it (iframe), passing the
sessionToken. - The user completes the verification (selfie + identity document) in the hosted app.
- When the decision is made, the platform POSTs the result and the sensitive customer data to your webhook endpoint as a signed
workflow.completedevent; your backend stores it and acknowledges with a2xx. - The hosted app returns the user to your site with the result — as query parameters (redirect) or a
postMessage(iframe).
Integration steps
1. Get an access token (backend)
Authentication uses the same OAuth2 model as the rest of the IDV Platform (see Capture & Workflow API → Authentication). Your backend requests an access token with the client-credentials grant, using the client credentials provisioned in Integration Settings. Keep the token server-side; it is short-lived, so cache it and refresh it before expiry.
The token endpoint is OpenID Connect standard and expects a form-encoded body, not JSON. TOKEN_URL below is the realm base — on the Innovatrics-managed service that is https://keycloak.idv.innovatrics.com/realms/integrators:
const response = await fetch(`${TOKEN_URL}/protocol/openid-connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});
const { access_token, expires_in } = await response.json();
Access tokens are short-lived. Cache the token for expires_in seconds rather than for a fixed period, refresh it before it expires, and retry once on a 401.
2. Create a session (backend)
Exchange the access token for a session token, optionally specifying the locale for the hosted app.
const response = await fetch(`${SERVICE_URL}/api/v1/session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${access_token}`,
},
body: JSON.stringify({
configuration: { locale: 'en' },
}),
});
const { sessionToken } = await response.json(); // return this to the front-end
3. Send the user to the hosted app (front-end)
Redirect flow — navigate the browser to the hosted app with the session token:
window.location.href = `${APP_URL}/?sessionToken=${sessionToken}`;
Iframe flow — embed the hosted app instead, adding viewType=iframe:
const iframe = document.createElement('iframe');
iframe.src = `${APP_URL}?sessionToken=${sessionToken}&viewType=iframe`;
iframe.width = '100%';
iframe.height = '100%';
iframe.allow = 'camera';
iframe.style.border = 'none';
document.body.appendChild(iframe);
4. The user verifies
In the hosted app the user provides a selfie and a photo of their identity document. The app uploads and processes them, then returns the outcome.
Getting the result
The overall outcome is delivered to the front-end, and the sensitive customer data is delivered to your backend.
Outcome (front-end)
Both flows return the same result: a single result field that is success or error.
-
Redirect flow — the user returns with the result as a query parameter. The user may not land on the page they started from.
https://your-site.com/verified?result=success -
Iframe flow — the result is delivered via
postMessageto the page that started verification. The message body is a JSON string, so parse it. Always validate the message origin:window.addEventListener('message', (event) => {if (event.origin !== 'https://your-site.com') return;let payload;try {payload = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;} catch {return; // not our message}const isSuccess = payload.result === 'success';});Guard the parse: other scripts on the page also post messages, and they will not all be JSON.
Which URL the user lands on.
| Ending | Landing page | result |
|---|---|---|
| Verification succeeded | verifiedUrl | success |
| Verification rejected — by the decision, or because a required NFC or palm capture could not be completed | rejectedUrl, or unverifiedUrl when no Rejected URL is set | error |
| User cancelled, or a technical failure ended the session | unverifiedUrl | error |
The result value alone does not tell a rejection from a cancellation — the landing page does.
The result parameter and the postMessage body both pass through the user's browser and must be treated as a display hint only. Confirm every verification server-side from the webhook below before granting access or creating an account.
Customer data (backend callback)
When the decision is made, the platform POSTs the result and the verified customer data to the webhook endpoints you registered in Integration Settings → Webhooks, as a signed workflow.completed event. It is sent for both a verified and a rejected result. Your backend must verify the signature, store the data securely, and respond with any 2xx to acknowledge receipt.
Delivery runs in the background, so the user can arrive on your landing page before the event arrives at your backend. A verification that goes to manual review sends no workflow.completed; the operator's later decision arrives as a record.action event.
The event uses the common webhook envelope; the result is in payload:
| Field | Description |
|---|---|
sessionToken | The session you created — match the event to it. For correlation only; it expires with the session. |
result | VERIFIED or REJECTED. |
timestamp | Completion time, Unix epoch milliseconds. |
customer_id · digitalIdentityId | Identifiers of the stored records, on the stored tier. |
customer.fullName | Full name of the verified user. |
customer.dateOfBirth | Date of birth. |
customer.placeOfBirth | Place of birth. |
customer.address.fullAddress | Full address. |
customer.documentNumber | Document number of the verified ID. |
customer.dateOfExpiry | Document expiry date. |
customer.personalNumber | Personal or national identification number. |
customer.fiscalNumber | Tax number, where the document carries one. |
customer.firstName · customer.lastName | Given name and surname, parsed separately. |
customer.sex | Sex as printed on the document. |
customer.document_type | Type of the presented document. |
customer.document_issuing_country · customer.document_nationality | Issuing country and nationality. |
customer.selfie | Base64-encoded selfie crop. Controlled by Include selfie in webhook in Integration Settings; on by default. Sent on the first delivery attempt only — a retry carries null. |
Empty customer fields are omitted. The full payload, including the per-factor trustFactors list, is described under Webhooks → workflow.completed.
type WorkflowCompletedEvent = {
eventId: string;
eventType: 'workflow.completed';
tenantId: string;
occurredAt: string;
payload: {
workflowId: string;
workflowRunId: string;
sessionToken?: string;
result: 'VERIFIED' | 'REJECTED';
timestamp: number;
customer_id?: string;
digitalIdentityId?: string | null;
customer: {
address?: { fullAddress?: string };
documentNumber?: string;
personalNumber?: string;
fiscalNumber?: string;
dateOfBirth?: string;
dateOfExpiry?: string;
firstName?: string;
lastName?: string;
fullName?: string;
placeOfBirth?: string;
sex?: string;
document_type?: string;
document_issuing_country?: string;
document_nationality?: string;
selfie?: string | null;
};
};
};
Fields are added over time, so ignore unknown properties rather than rejecting the payload — a strict schema will start failing when the next field ships.
Verifying the callback signature
Each delivery carries an X-IDV-Signature header, sha256= followed by the hex HMAC-SHA256 of the raw request body, computed with the signing secret of the webhook endpoint. Verify it before acting on the payload:
const { createHmac, timingSafeEqual } = require('node:crypto');
// rawBody must be the bytes as received — not a re-serialized object.
const expected =
'sha256=' + createHmac('sha256', WEBHOOK_SIGNING_SECRET).update(rawBody).digest('hex');
const received = req.headers['x-idv-signature'] ?? '';
const ok =
expected.length === received.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!ok) return res.status(401).end();
Capture the raw body before any JSON middleware parses and re-serializes it — re-serializing changes key order and whitespace, and the digest will never match. A Python example is under Webhooks → Verifying authenticity.
Deliveries can repeat. Key your processing on eventId so a duplicate is a no-op rather than a second account.
Error handling
Errors surface as HTTP status codes and are handled inside the hosted app; you only need to handle errors during app initialization and while retrieving the result. For security, the hosted app does not expose error details — each error carries a unique tracing ID. If you hit an error, contact Innovatrics with that tracing ID for analysis.
See also
- Web Components — embed capture in your own web UI instead of redirecting
- No-code / Low-code
- Webhooks & Callbacks
- Integration Overview