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 | Default flow language. |
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.
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. Overrides are keyed by the flow's i18n dictionary and validated against
a curated 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 a version and per-locale title / body;
editing the content bumps the version and pushes the superseded copy onto its
history, and an acceptance record is captured against the session.
Warning
Common pitfall. If T&C acceptance is required but a locale is missing
title/body copy, applicants in that locale can land on a start screen with
fallback or empty text and hesitate to accept — which looks identical to "the
applicant is stuck on a step." Before shipping a new locale, confirm both
Settings → Translations and Settings → Terms have copy for it, not
just one.
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.