Concepts
Customize the flow
Branding, theme, locale, prefill and copy overrides for the hosted verification page.
The hosted flow is white-labelled: it renders with your logo, colours, copy and
theme so the applicant sees your brand, not ThirdFactor's. Most styling is a
tenant/workflow-level property set in the console — it applies consistently
across every channel you send an applicant through. Only a small set of knobs
(locale, prefill) is set per session at create time.
What is set where
| Aspect | Where it's configured | Per session? |
|---|---|---|
| Logo, colour, button shape, theme | Console Config → KYC Workflows → Design panel, and tenant defaults in KYC settings | No — tenant/workflow-level |
| Applicant-facing copy (per-locale overrides) | Console Settings → Translations | No — tenant-level |
| Terms & Conditions | Console Settings → Terms | No — tenant-level |
| UI language | locale on POST /v3/session/ | Yes |
| Pre-populated identity fields | prefill on POST /v3/session/ | Yes |
Note
There is no per-session branding override on the API. Colour, logo and theme are tenant/workflow properties by design — that consistency is what keeps the applicant's experience the same whether they arrive via a link, an SDK, or a console-generated URL.
Branding & theme
The Design panel in the workflow builder controls the look of the hosted page. Workflow-level values override tenant defaults, so a designer sees exactly what they styled. The customization blob the flow consumes supports:
| Key | Type | Effect |
|---|---|---|
primary_color / color | string | Accent colour for buttons and highlights. |
logo_url | string | Brand logo shown in the flow header (falls back to the tenant's client_logo_url). |
button_radius | sm | md | full | Corner rounding of primary buttons. |
theme | light | dark | Forces the flow's colour scheme. |
language | en | ne | any of your account's languages | Default flow language (the Configurations picker lists your account's languages). |
welcome_title / welcome_message / button_text | string | Intro-screen copy (session vocabulary). |
headline / body / cta_text | string | Intro-screen copy (Design-panel vocabulary). |
allow_upload | boolean | Allow file upload for document capture (default true). |
allow_desktop | boolean | Allow completing on desktop (default true); when desktop is allowed you can also show a "continue on your phone" QR. |
Put together, that's the shape the flow reads at render time — an operator never edits this JSON directly (the Design panel writes it), but it's useful to see the whole object when you're debugging why something isn't rendering as expected:
{
"primary_color": "#0F62FE",
"logo_url": "https://cdn.acme.example.com/logo.svg",
"button_radius": "md",
"theme": "dark",
"language": "en",
"welcome_title": "Verify your identity",
"welcome_message": "This takes about 3 minutes. Have your ID ready.",
"button_text": "Start verification",
"allow_upload": true,
"allow_desktop": true
}Info
These keys are operator-authored in the console, not accepted on the create-session API. The API-created session inherits whatever the tenant and its resolved workflow have configured.
Warning
Common pitfall. Setting logo_url or primary_color at the tenant level
and expecting it to apply everywhere, then being surprised a specific workflow
still shows the old look — workflow-level Design values override tenant
defaults. If a workflow was styled before you updated the tenant default, that
workflow keeps its own values until someone re-saves its Design panel.
Dark mode
Set theme to dark (or light) in the Design panel to force a colour scheme.
The hosted flow resolves the theme server-side from the session token before
the first frame, so a dark flow paints dark immediately — there is no light
flash before hydration on the loading and gate screens.
Locale
locale on POST /v3/session/ sets the UI language for that specific session
(default en):
Request
{
"workflow_id": "9c2f1c4e-9a2b-4f2f-8f0f-0f1f3f4f5f6f",
"locale": "ne"
}Response — locale itself isn't echoed back on the session object, but it
takes effect immediately on the hosted page:
{
"id": "6f0e2b7a-…",
"session_kind": "user",
"status": "not_started",
"url": "https://acme.example.com/verify/<session_token>",
"vendor_data": null,
"decision": "in_progress",
"expires_at": "2026-07-13T08:00:00Z",
"created_at": "2026-07-12T08:00:00Z"
}The flow ships English (en) and Nepali (ne) copy. Per-locale text can be
overridden tenant-wide (see below), so locale selects which override set the
applicant sees.
Your account's languages
Beyond English and Nepali — always available — your account can offer the
primary language of every country enabled for it (each country in the
platform's country documents has one, e.g. India → Hindi). An operator adds
them under Settings → Translations (or from a questionnaire's "Editing in"
row, Settings → Terms & Conditions or the workflow editor) with + Add
language; that one list drives Translations, questionnaire translations,
Terms & Conditions, the workflow editor's preview and default-language pickers
and the hosted page's header language dropdown, where each language shows with
its flag. Picking a language there switches the whole
flow, and any string not translated into it falls back to English, one string
at a time. locale on POST /v3/session/ accepts any of your account's
languages too.
If a country is later switched off for your account, its language leaves the dropdown and the "+ Add language" list — but its translations are kept, and come back as soon as the country is enabled again.
Note
Sending an unsupported locale value doesn't error the create call — the flow
falls back to its default (en) copy for anything it doesn't have a translation
set for. If an applicant reports seeing English when you expected Nepali, check
the exact locale string you sent before assuming a bug.
Copy overrides (translations)
Under Settings → Translations an operator can override any applicant-facing
string per locale — for example the intro title, step instructions, or button
labels. Every applicant-facing string of the hosted flow is listed — screen
copy, buttons, the welcome screen's step names, the Terms & Conditions consent
line, questionnaire field chrome and validation messages — so a language you
add can cover the entire flow. Overrides are keyed by the flow's i18n
dictionary and validated against that catalog, so only real, applicant-safe
strings can be set. They are merged into the flow at render time and honour the
session locale.
{
"en": { "kyc.intro.title": "Verify your identity with Acme Bank" },
"ne": { "kyc.intro.title": "एक्मे बैंकसँग आफ्नो पहिचान प्रमाणित गर्नुहोस्" }
}Tip
Because overrides are validated against a curated key catalog, you can't accidentally inject markup or break the flow's layout — an unknown key is simply not applied. If your override doesn't show up, double-check the exact dictionary key rather than the text itself.
Terms & Conditions
Under Settings → Terms you can attach versioned Terms & Conditions to the
flow's start screen, optionally requiring explicit acceptance before the
applicant can proceed. T&C carry one version and a title / body per
language — any language on your account's list. Editing the content in any
language bumps the version and pushes the superseded copy onto its history.
The platform also maintains default terms for every language it supports. For each language the applicant reads, the flow shows your own text if you wrote it, otherwise the platform's default for that language, otherwise your English terms, otherwise the platform's English default — title and body each on their own. Switching language in the flow's header switches the terms and their consent line too.
The acceptance record captured against the session names the version, the
locale the applicant used, the language of the text actually shown, its
source (tenant or platform, with the default's own platform_version)
and a content_sha256 of the exact words — the same version check applies in
every language.
Warning
Common pitfall. A language with no terms of its own and no platform default shows your English terms — applicants reading, say, Hindi then accept English text. Settings → Terms marks each language (your own text, platform default, or falls back) so you can see this before shipping a new language; confirm both Settings → Translations and Settings → Terms cover it.
Prefill
prefill on POST /v3/session/ pre-populates identity fields so the applicant
types less. Only a fixed set of keys is read (unknown keys are ignored):
{
"prefill": {
"full_name": "Aarav Sharma",
"first_name": "Aarav",
"last_name": "Sharma",
"display_name": "Aarav",
"date_of_birth": "1990-04-12",
"email": "[email protected]",
"phone": "9841000000",
"nationality": "NP",
"address_line1": "House 12, Baneshwor",
"address_line2": "Ward 10",
"city": "Kathmandu",
"state": "Bagmati",
"country": "NP",
"zip_code": "44600"
}
}See Create a session for the full field reference.
Warning
Prefill is a convenience, not a source of truth. The workflow still verifies the applicant's real document and biometrics — prefilled values don't shortcut any module or influence the decision.
Hosted flow vs. SDK customization
Both delivery paths render the same Design-panel branding — the difference is what else you can control from the host application:
| Hosted flow (redirect) | SDK (embedded) | |
|---|---|---|
| Logo, colour, theme, button shape | ✅ tenant/workflow Design panel | ✅ same Design panel |
| Copy per locale | ✅ Settings → Translations | ✅ same, via locale |
| Container styling (size, position, host page around it) | ❌ full-page, you don't control the chrome | ✅ your app controls the embedding container |
| Light/dark resolved before first paint | ✅ server-side from the session token | ✅ same |
| Per-session prefill | ✅ prefill at create time | ✅ same field, same endpoint |
Troubleshooting
Warning
Applicant stuck on a step because of a customization gap. The most common
cause isn't a broken module — it's missing copy for the locale the applicant is
actually using. Check, in order: (1) does Settings → Translations have an
entry for locale, (2) does Settings → Terms have T&C copy for that same
locale if acceptance is required, (3) is theme forcing a colour scheme that
makes text unreadable against a custom logo_url. Pull
GET /v3/session/<id>/logs/ to see the exact step the applicant stalled on.
Note
Branding changes made in the console Design panel apply to new sessions
created after the save — a session already created (and its url already
handed to an applicant) keeps rendering with whatever was configured when the
flow first resolved the token. If a design change "isn't showing up," confirm
you're testing with a freshly created session, not a stale link from before the
change.