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

AspectWhere it's configuredPer session?
Logo, colour, button shape, themeConsole Config → KYC Workflows → Design panel, and tenant defaults in KYC settingsNo — tenant/workflow-level
Applicant-facing copy (per-locale overrides)Console Settings → TranslationsNo — tenant-level
Terms & ConditionsConsole Settings → TermsNo — tenant-level
UI languagelocale on POST /v3/session/Yes
Pre-populated identity fieldsprefill 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:

KeyTypeEffect
primary_color / colorstringAccent colour for buttons and highlights.
logo_urlstringBrand logo shown in the flow header (falls back to the tenant's client_logo_url).
button_radiussm | md | fullCorner rounding of primary buttons.
themelight | darkForces the flow's colour scheme.
languageen | ne | any of your account's languagesDefault flow language (the Configurations picker lists your account's languages).
welcome_title / welcome_message / button_textstringIntro-screen copy (session vocabulary).
headline / body / cta_textstringIntro-screen copy (Design-panel vocabulary).
allow_uploadbooleanAllow file upload for document capture (default true).
allow_desktopbooleanAllow 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.

FAQ