Guide · read once, then keep it open

Configure an onboarding journey for a country

Follow these eight steps and you will finish with a complete, validated onboarding configuration for a new country — journey, forms, account products and wording — rendering correctly on web and mobile, downloaded as a change bundle you raise as a pull request. No JSON by hand, and no engineer needed to author it.

≈ 45 min for a first pass No coding Nothing goes live without a merge Safe to practise in offline mode

◆ How config fits together#

Three layers. Understanding them makes every screen in the studio obvious, and prevents the one mistake that causes real damage: editing something shared when you meant to change one country.

Base form

One shared definition per form — basic_info, address, ekyc… It defines which fields exist and how they behave everywhere.

+
Country overlay

A small patch holding only what this country changes — optionally narrowed to one channel (web / android / ios / ussd).

+
Journey

The order of screens for this country, plus the conditional branches (FATCA, CRS, extra documents) and where the customer rejoins the main flow.

What follows from this

  • You never edit a base form to serve one country. Open the form for your country and change what differs; the studio records just the difference. Kenya renaming “State” to “County” is a two-line overlay, not a copy of the form.
  • A journey references forms, it doesn't contain them. Reordering steps never touches field definitions.
  • Precedence is base → all-channel overlay → channel overlay. A web-only overlay wins over an all-channel one, for web only.

▸ Set up the studio#

npm install
npm start        # then open the URL it prints

Requires Node 24.15 or newer. If npm start complains about your Node version, switch it (nvm use 26) and try again.

Three things to know before your first session:

Your draft is local A new-country workspace lives in this browser, on this machine. It is not shared with teammates and does not sync. Don't clear site data mid-task, and don't expect a colleague to pick up your draft — download the bundle, raise the pull request, and collaborate there instead.
One draft at a time The studio holds a single new-country workspace. Discard workspace deletes it permanently.
The studio hands you files It never writes to the repository and never deploys. You finish by downloading a change bundle — a zip of the exact files, at the exact paths the service expects — then add them to the DigitalOnboardingSvc repo and raise the pull request yourself. A human reviews and merges; only then does the lower environment pick it up.

Offline mode — the safe sandbox. Out of the box the studio reads seed data bundled into the app rather than reading it from the service. Nothing you do can affect the repository either way, so it's the way to learn the tool without consequences.

The catch is staleness: a bundled snapshot only knows the countries it was built with, so a country published to the service since then won't appear. Every bundle you download records which source it came from and when. Check that stamp before handing a bundle to anyone — configuration built on a stale base is the one mistake this mode makes easy.

1 Create the workspace#

Catalogue → New country · about 2 minutes

Always start by cloning a country that already works. You inherit a journey that is known to be valid and adapt what differs — far faster and safer than assembling one from nothing.

FieldWhat to enter
Country codeThe 2-letter ISO code, e.g. TZ. Rejected if that country already exists.
Display namee.g. Tanzania.
EnvironmentTesting (lower env) while you build.
ChannelsThe channels this country will actually launch on. For web and mobile, tick web, android and ios.
Clone fromThe country closest to your target. Pick Start from scratch only if nothing resembles it — you would then build every step by hand.
Also copy overlaysLeave on. You inherit the source country's form tweaks and can remove what doesn't apply.
Deep-copy country dataLeave on to copy the source account products and privacy policy instead of starting with an empty account catalogue.
Local currencyConfirm the new country's three-letter currency code. Products in the source country's local currency are remapped to it; foreign-currency products such as USD, EUR and GBP keep their original currency.

Press Create workspace →. The studio deep-copies the source journey — steps, branches, workflow settings, exit rules — and rewrites its wording keys from the source country to yours (retail_onboarding.ke.step… → …tz.step…), carrying the source's text over as your starting point. When country-data copying is selected, the account products and privacy policy are independently deep-copied and the local product currency is remapped to the code you confirmed.

You land on the workspace hub: three numbered sections that double as your to-do list. You can leave and return at any time; the draft persists in this browser.

Done when
  • The hub header shows your country, its environment badge and the channels you chose.
  • Section 1 lists a journey with a step count (e.g. “7 main steps · 2 branches”).

2 Shape the journey#

Hub section 1 → Edit journey · every change auto-saves to your draft

This is where you decide what happens in what order. The Flow map at the top is a read-only picture of the current path — check it before and after editing.

Main steps

  • Order is the customer's experience. Reorder with the ↑ / ↓ arrows.
  • Step type matters — see the types table. Use FORM for data entry, REVIEW_FORM / CONFIRM_FORM for “check your details” screens, CONSENT_FORM for accept/decline, STATUS_PAGE for a wait-while-we-check screen, and INFO for a screen with no form attached.
  • Form is the base form the step renders — choose from the list. A typo becomes a publish-blocking error rather than a production surprise.
  • Title must be a wording key, not literal text (e.g. retail_onboarding.tz.step.basic_info.title). Literal text still publishes but raises a warning, and customers see that same string in every language.
  • Remove steps this country doesn't need. That never deletes the form — other countries keep using it.

Workflow settings

Three values the backend needs. Both step references must name main steps (not steps inside a branch), or publishing is blocked.

initiateAfterStepId

The step after which account opening kicks off — usually the first data-capture step.

completeOnStepId

The step that finishes the journey.

otpEnabled

Whether the workflow sends a one-time password.

Branches — the conditional detours

SettingMeaning
Branch idUnique name, e.g. fatca.
Trigger after stepThe main step after which the condition is evaluated.
Resume before stepWhere the customer rejoins the main flow once the detour ends.
Conditionall (every part must pass) or any (one is enough). Each part reads a field captured in an earlier step, using either an operator (EQUALS, IN, IS_NOT_EMPTY…) or a named predicate (isFatcaCountry, isCrsCountry).
Branch stepsThe screens inside the detour, in order.

The rule people trip over. A condition may only read a step at or before its trigger — you cannot branch on an answer the customer hasn't given yet. The linter says so plainly if you get it wrong.

Exit rules (on branch steps) end a journey early — for example when a customer declines a mandatory waiver. Each needs a condition, the action terminate_journey, and a reason.

Read the Lint panel before you leave

It sits at the bottom of the builder. Errors block publishing; warnings are advice worth taking. Aim for zero errors now rather than at handoff time — see what blocks a handoff.

Done when
  • The flow map reads like the journey you intend, in order.
  • The lint panel says references resolve and anchors are valid — zero errors.
  • You've pressed Done — back to workspace →.

3 Adapt the forms#

Hub section 2 → Edit · the longest step, and the one that decides how screens look

The table lists every form your journey touches, whether it already carries overlays, and an Edit → link. Open only the forms that genuinely differ for your country; inherited overlays are often already correct.

First, set the scope

Scope decides where your change lands, and it is the most common thing to get wrong.

All channels

The change applies everywhere in this country — web and mobile alike. Start here for wording, validation and new fields.

Only {channel}

A channel-specific tweak, e.g. a different layout on web. Switch the Channel selector first, then choose “Only …”.

Making one form work on web and mobile. You do not build two forms. One set of fields and rules serves every channel; the apps lay them out differently — mobile stacks fields one per row, web pairs them two-up. So:

1 · Author fields, validation and wording under All channels. 2 · Check the preview in web, then android/ios. 3 · Only if a channel genuinely needs a different arrangement, switch to Only {channel} and adjust the layout there.

Then work through the tabs

TabUse it to
FieldsAdd or remove fields; set type and widget; mark required; point at the label and placeholder wording keys; define dropdown options; and set per-field validation — length, pattern, format, plus the error-message key for each rule.
LayoutOrder fields and group them into rows. A field with no layout row is invisible to the customer — the preview flags exactly this.
RulesRules spanning several fields (“if A is filled, B is required”). Write the expression, give it an error key, and test it against sample data right there. Single-field validation stays in the Fields tab.
Raw JSONThe escape hatch — the resolved schema and UI config. You shouldn't normally need it.
TranslationsThe wording behind every key this form uses, per language, with gaps called out.

Watch the live preview as you type

The preview on the right renders the screen the way the selected channel's app renders it, and it is interactive: choose values, watch conditional fields appear, watch validation fire. Fill sample populates plausible data in one click.

Below it, Generated change shows the exact patch your edits produce. If that panel is empty you haven't changed anything that differs from the base — which is a perfectly good outcome.

Finish with Save to workspace →. Overlays don't publish individually here; they're bundled into the country's single change set.

Done when
  • Every form that needed changing shows an overlay count on the hub table.
  • Each edited form's preview looks right in web and in android.
  • No field you intended to show appears under “Not shown to the customer”.

4 Account products, privacy & consent#

Hub section 3 → Edit country data · /country-data/{CC}

Not every piece of country configuration is a form. The account the customer picks, the currencies on offer and the privacy notice are database-backed country data: the app fetches them from onboarding lookup endpoints. Terms & conditions and other consent documents remain linked content on their FormEngine forms. A country with a perfect journey but no account products still cannot onboard anyone.

Account products

One entry per account the customer can open, keyed by currency + residency + scheme code — that combination must be unique, and the studio blocks publishing if you repeat one. Each product carries what the picker renders: account type, description, features, requirements, the tariff guide link, and the optional cost/special notes.

Currencies are derived, not authored. The currency list a client sees is simply the distinct currencies across this country's account products — add a product in USD and USD appears. The service builds the list the same way and then describes each code from the shared DataLookup catalogue. Removing an editor row does not delete an existing database row; deletions need an explicit reviewed migration.

Cloning a country copies its products as a starting point. Always revisit them: scheme codes, requirements and the tariff guide are country-specific, and a copied SB100 from Kenya is almost certainly wrong elsewhere.

Privacy and consent documents

The privacy notice is a database-backed lookup. Configure its published document link, journey, content type (url) and media type (application/pdf) here. It must use https. Use Open the document ↗ to confirm the link resolves before publishing — a 404 here is a legal problem, not a UI one.

Follow the service's current ownership. Terms & conditions has no lookup of its own: configure it in the confirm form editor, where the live app reads x-linkedContent.links[]. Configure other consent documents in their corresponding form's x-linkedContent. Those edits publish as FormEngine country patches.

What this produces

One native EF data migration under OmniDos.Onboarding.Infra/Migrations. It upserts account products and privacy-policy rows through the database-migration path DigitalOnboardingSvc already runs at startup. In a draft workspace it goes out with the rest of the change set; for a live country it downloads as a bundle of its own.

Done when
  • Zero blocking issues on the country data screen.
  • Every account the country actually offers is listed, in every currency it offers.
  • The currency chips match what you expect a customer to see.
  • The privacy link opens the right, current document.
  • Terms and other consent links are configured on their service-owned forms.

5 Wording & languages#

Form editor → Translations tab

Every visible string is a key (identity.documentNumber.label), and the text behind it is authored per language. Nothing in the config holds literal customer text — that's what makes one form serve many languages. The service swaps the key for the text as it answers the request, so a customer can switch language mid-journey and the form itself never changes.

  • The Translations tab lists the keys this form uses and which languages are missing. Fill the gaps for every language the country launches in.
  • In the preview, switch Lang to check each language, and toggle keys to reveal the raw key behind any string.
  • If you see a key where you expect words (e.g. address.county.label), that key has no text for the selected language. Add it.

Shared wording. Form-level keys are usually shared across countries, so changing that text affects every country using it. When wording should differ for your country only, point the field at a country-specific key instead.

Done when
  • Walking the journey with keys off shows real sentences on every screen — no dotted keys.
  • Each launch language is filled for the keys your country introduced.

6 Walk it as a customer#

Hub → Preview journey (or /preview/{CC}) · the step that catches what reading config never does

Walk the whole path, exactly as a customer would:

  1. Fill sample fills the current screen; Fill & next fills and advances. Or type real values to test a specific case.
  2. The step rail shows the whole path; branch steps carry their branch id. Click any step to jump.
  3. The Branches tab marks each branch entered or skipped — with the value that decided it, so “why didn't FATCA fire?” answers itself.
  4. The Data tab shows what's been captured step by step, with Copy JSON for the whole payload.
  5. Switch channel and Lang to check every surface and language.

Read the two panels below the phone

Blocking the CTA

Why the Continue button is grey — field by field, with the rule that isn't satisfied.

Not shown to the customer

Fields that exist in the config but will never render: no layout row, hidden by design, or waiting on a visibility rule (it names which).

Cover these cases before you publish

  • Web from first screen to last.
  • Android / iOS from first screen to last — the mobile layout is one field per row and has a sticky button.
  • Every branch you expect to fire — change the country of residence, decline a waiver, leave optional fields empty.
  • Every language the country launches in.
  • The review / confirm screens — do they show the data captured earlier?

What the preview mocks. Camera and fingerprint steps show a stand-in image rather than using a real device — deliberately, so you can walk past a capture step and check everything after it. Named branch predicates (isFatcaCountry, isCrsCountry) are approximated; the backend decides for real. Treat branch entry here as a strong signal, then confirm in the lower environment after merge.

Done when
  • You reached “Journey complete” in web and in mobile.
  • Each branch you expect entered when you gave it the triggering answer.
  • No screen showed a raw wording key or an unexpected empty field.

7 Review & download#

Hub section 4

The final section shows the complete change set — the journey file, one file per overlay, the country data migration, the translation store and one copy catalogue per language — with every path listed. The download is blocked while any lint error remains, and the panel tells you which.

Download {CC} bundle → gives you a single zip containing everything, which you hand to the repository in step 8. Inside:

In the zipWhat it's for
repo/ Your files, already at the paths the service expects. Copy this over the repo root and everything lands where it belongs — no deciding where files go.
PR.md A ready-written pull request description: what changed, which country, every file, and the review checklist. Paste it in rather than writing one.
manifest.json Where the configuration was read from and when — the record that says the bundle wasn't built on stale data.
README.txt The step-8 instructions, for whoever opens the zip without this guide open.

See what's in the bundle if you want to understand the files themselves.

Done when
  • The file list matches what you changed — nothing unexpected.
  • Zero blocking issues.
  • The zip is on your machine and its manifest.json names a source you trust.

8 Raise the pull request#

The studio has no access to the repository — by design, so it never holds a credential that could write to it. You take the bundle the last few metres.

You need repo access for this step. It calls for a clone of DigitalOnboardingSvc and enough Git to push a branch. If you don't have both, hand the zip to someone who does — everything they need is inside it, and nothing about the configuration changes in transit.

  1. Unzip it and copy the files in. The repo/ folder mirrors the repository's own structure, so a single copy puts every file at its correct path:
    unzip {CC}-config-bundle.zip
    cp -R {CC}-config-bundle/repo/. /path/to/DigitalOnboardingSvc/
    cd /path/to/DigitalOnboardingSvc && git status
    git status should list exactly the files the studio showed you in step 7. If it lists more, you copied from the wrong level.
  2. Branch, commit and push.
    git checkout -b config/{cc}-onboarding
    git add . && git commit -m "Add {CC} onboarding configuration"
    git push -u origin config/{cc}-onboarding
  3. Open the pull request against the lower-environment branch your team uses, and paste PR.md as the description. It already lists the country, the files and what reviewers should check, so there's nothing to write.
  4. Get it reviewed — ideally by someone who can re-walk the journey. Give them the review checklist.
  5. Merge — the lower environment then picks the configuration up.
  6. Verify in the lower environment, especially anything the preview only approximates: branch predicates, real document capture, the actual workflow and OTP.
  7. Then talk about production.

Don't skip step 6. The studio guarantees the config is structurally valid and shows you how it renders. It cannot prove the backend's predicates, capture SDKs or workflow behave as you expect. One pass through the lower environment closes that gap.

✓ You're finished when…#

  • The journey lints clean — zero errors, warnings understood.
  • Every form the journey uses renders correctly on web and mobile.
  • Required fields, formats and cross-field rules behave in the preview.
  • Branches enter and resume where you intended.
  • No raw wording keys in any launch language.
  • Account products cover every currency the country offers, and both legal links open.
  • The change set is one PR, reviewed and merged.
  • Behaviour confirmed in the lower environment.

▤ Change a country that already exists#

No workspace is involved — you're editing live configuration, so changes are handed off one at a time, each as its own small bundle and its own pull request. Everything else (scope, tabs, preview, lint) works exactly as above.

TaskWhereThen
Journey change
add / reorder / remove a step, adjust a branch
Catalogue → country → Open journey builder → Preview it, resolve lint, then Download journey bundle.
Account product, currency or privacy link
add an account or change the privacy URL
Catalogue → country → Country data → Resolve any blocking checks, then Download database migration bundle.
Terms or consent link
change linked legal content
Catalogue → country → the owning form editor → Edit x-linkedContent, preview it, then download the country overlay bundle.
Form change for one country
relabel a field, add validation, adjust layout
Catalogue → country → form Edit → Set the scope, change, check the preview, then Download overlay bundle.

◎ Reviewing someone else's change#

You don't need to read patch JSON to review well. Ask five questions:

  1. Does the description match the files? A “relabel one field” change shouldn't touch the journey.
  2. Is the scope right? A channel-specific file (…_web_ui_patch.json) where the change should apply everywhere — or the reverse — is the most common mistake.
  3. Are titles and labels wording keys, with translations included? A change that adds keys but touches neither translations/translations.json nor Locales/{lang}.json shows raw keys to customers — the service falls back to English and then to the key itself.
  4. Do branch anchors still make sense if steps were reordered?
  5. Has the author walked it? Ask which branches fired and which channels they checked.

The fastest review is to check the branch out, run the studio, and walk the journey yourself.

⟨⟩ Hand it to the client teams#

The web, iOS and Android apps don't read the files you publish — they call the onboarding API and render whatever comes back. API Response Mock shows exactly those payloads, rebuilt from your configuration, so a client team can stub against them before anything ships.

Open it from the sidebar (Client contract → API Response Mock), from a country page, or from the journey builder. Address: /api-mock/{CC}. It is read-only — nothing you do there changes configuration.

What's on the page

PanelMirrorsUse it for
Journey config response GET /journey-configs?countryCode=&journeyId=&channel= The flow the client renders: steps, sequence, step types, workflow anchors, branches.
Lookup responses GET /onboarding/lookups/{currencies, account-products, privacy-policy} Country data — the account picker, the currency list and the legal documents.
Form config responses the form object on GET /onboarding/onboardings/{id}/steps/{stepId} One per step — the resolved schema + uiSchema the client builds the screen from.

Pick the channel before you copy. The page resolves the same way the service does: a variant matching that channel exactly, otherwise the channel-less one, with channel-specific overlays applied on top of the shared ones. Switching between web and ios is the quickest way to prove a channel overlay actually lands.

Taking it away — three buttons

ButtonYou getGood for
Copy all JSON One bundle on the clipboard: the journey config response plus every step's form config response. Pasting straight into a chat or a ticket.
Download .json That same bundle as a single file. Attaching to a ticket or an email.
Download .zip A folder with each config as its own file — journey-config.json, forms/{formKey}.json one per step, lookups/*.json, and a README.txt recording the country, journey, channel, endpoints and the caveats below. Client teams who want one fixture file per response in their test suite.

Each panel also has its own Copy JSON, and so does every JSON block elsewhere in the studio (raw form JSON, generated patches, the journey seed file, the workspace change set, the walkthrough's captured payload).

The bundle's outer wrapper is studio metadata. In the single-file bundle, only journeyConfig and the entries of formConfigs are verbatim API payloads — generatedBy and request just record what was generated and for which country/journey/channel. In the zip there is no wrapper at all: every .json file is exactly one response.

Four things the mock deliberately doesn't fake

  • hash and context. The live form payload carries a server-computed hash of the resolved documents plus a context dictionary. Both are produced at resolve time, so the mock leaves them out rather than inventing values — clients should treat them as present but opaque.
  • Branch conditions stay server-side. A branch reaches the client with triggerHint only; the real condition tree and any step exit rules are evaluated by the backend and never serialized.
  • otpEnabled is internal. Clients see workflowConfig with initiateAfterStepId and completeOnStepId only.
  • Names on the wire differ from the seed files. Step types are PascalCase (REVIEW_FORM in the file becomes ReviewForm in the response), and nulls are serialized rather than dropped — a branch with no resumeBeforeStep still shows "resumeBeforeStep": null.
  • A STATUS_PAGE step carries no form. It resolves one server-side, but the client receives "form": null and polls the step's status endpoint instead.

It shows what you've configured, not what's deployed. For an existing country the mock reflects the published seed data; inside a draft workspace it reflects your unsaved draft. Say which one you sent when you share it.

While editing a single form there's a smaller version of the same thing: the API mock tab in the form editor shows that one form's payload with your current, unpublished edits applied.

! What blocks a handoff#

Three independent checks. Use all three — they catch different classes of mistake.

1 · Journey lint — structure

Errors block the download:

  • A step with no id, or a duplicate id.
  • A step whose type needs a form but has none, or that references an unknown form.
  • A NewGen group (sourceReference) on a step that isn't a FORM step, a group NewGen doesn't return (contacts, preferredAddress, sourceOfIncome, biometrics), or one group mapped by two steps of the same journey.
  • Workflow settings pointing at something that isn't a main step.
  • A branch whose trigger or resume anchor isn't a main step, that has no condition, or whose condition reads a step after its trigger.
  • An exit rule with an empty condition or an unknown action.

Warnings — fix if you can: a title that isn't a wording key; no workflow settings; a branch with no steps; an exit rule with no reason.

Step ids don't have to match their form key, and one form may back several steps — the service binds each step to its own formKey. Country review also warns when a form mapped to a NewGen group has no property for one of that group's parameters.

2 · Form validation — per screen

Per-field rules (required, length, pattern, format) and cross-field rules. The preview enforces both live: the button stays grey until the screen would pass, and Blocking the CTA names each offender.

3 · The walkthrough — sequence

The only check that covers data carried between steps, branches firing, review screens filling themselves in, and each channel rendering.

⌸ What's in the bundle#

Database-backed country data is its own migration. It upserts account products and privacy policies under the Onboarding module. Consent content that the service reads from forms remains a FormEngine country patch.

Under repo/ the studio writes files the backend already understands, at the paths it already reads. The naming is a contract, not a preference:

WhatPath in the repository
Journeysrc/Modules/FormEngine/OmniDos.FormEngine.Infra/SeedData/JourneyConfig/{CC}.json
Country overlaysrc/Modules/FormEngine/OmniDos.FormEngine.Infra/SeedData/Patches/{CC}/{form}_{cc}_v{n}_[{channel}_]{schema|ui}_patch.json
Country datasrc/Modules/Onboarding/OmniDos.Onboarding.Infra/Migrations/{timestamp}_Seed{CC}CountryData.cs
Translations (authoring)translations/translations.json
Translations (what the service reads)src/Modules/FormEngine/OmniDos.FormEngine/Locales/{lang}.json

Wording ships as two shapes of the same thing. The config holds keys, never text; the service substitutes the text as it answers each request, reading one flat key → text catalogue per language according to the caller's Accept-Language. Those catalogues are what runs. The file under translations/ is the pivot of them — every language for a key on one row, which is the only shape a person can actually edit — and the studio generates the catalogues from it, so both ship in the same pull request as the config that introduced the keys.

The catalogues are whole-file replacements. They are written from the entire store, not just the keys you touched, so copy them in as they come. And a language the service doesn't list in FormCopyLanguages.Supported — today en and fr — is generated but never loaded; the Translations screen flags that, and adding it there is a one-line backend change that belongs in the same PR.

Overlays are precise lists of edits against a base form version, not copies of it. That's why a small change stays a small file — and why improving a base form still reaches every country.

? Troubleshooting#

“Could not load base form …”

The studio couldn't fetch that form's seed files. Usually a stale browser tab pointing at a dev server that is no longer running — hard-reload the page. If it persists, check the browser's Network tab for the failing mock/forms/… request and confirm the form exists at that version.

Strings show as dotted keys instead of words

That key has no text for the selected language. Add it in the form editor's Translations tab. In a cloned draft, keys re-keyed to your country fall back to the source country's wording until you author your own.

The Continue button stays grey

Read Blocking the CTA under the phone — it names each field and why. Usual causes: a required field with no value, a pattern or format mismatch, or a cross-field rule that isn't satisfied.

A field I added doesn't appear

It has no layout row. Add it in the Layout tab — the Not shown to the customer panel lists exactly this case.

“{CC}'s journey isn't complete enough to edit”

The journey file exists but is missing parts the studio needs to render it faithfully — usually workflowConfig, sometimes a variant with no steps. Rather than open an editor that fills those gaps with defaults (which would then be saved back as if you had chosen them), the service withholds the journey and lists what's missing.

Add the named fields to SeedData/JourneyConfig/{CC}.json and reload. This is normal for countries that were scaffolded but never finished.

A branch never fires

Open the Branches tab in the preview: it shows each condition part and the value it read. Usually the field is empty (an earlier step wasn't filled), the source step comes after the trigger, or the value doesn't match what the condition expects.

Download is disabled

Either nothing has changed, or lint errors remain. The footer of the panel says which.

My draft vanished

Drafts are saved by the studio server and associated with this browser through a secure session cookie. Refreshing or reopening the studio keeps the draft; a different browser, cleared cookies, or a different studio deployment starts a different session. Download the bundle before clearing your session or moving to another deployment.

git status shows files I didn't change

You copied from the wrong level. It must be the contents of repo/ that land on the repo root — note the trailing /. in cp -R {CC}-config-bundle/repo/. /path/to/DigitalOnboardingSvc/. Without it you get a nested repo/ directory instead. git checkout . to undo, then copy again.

≡ Tables & conventions#

Step types

TypeNeeds a formUse for
FORMyesOrdinary data capture.
REVIEW_FORMyes“Let's confirm your info” — prefilled read-only summary with Looks good / Change details.
CONFIRM_FORMyesFinal check before submitting.
CONSENT_FORMyesAccept or decline a document (waiver, declaration).
STATUS_PAGEyes, but the client never sees itA waiting screen while the service checks something with a third party (TZ: nida_validation). See below.
INFOnoAn informational screen with no fields.

STATUS_PAGE, in more detail

This one behaves unlike the rest, so it's worth reading before you use it.

  • It binds a form, but the client is sent form: null. The schema is there so the service can validate the data it fetches itself and pass it to later steps via x-source — for TZ, the name and date of birth NIDA returns, which review_information then displays. Leave the form's fields x-hidden and its layout empty; nothing there is ever rendered. You must still set formKey and formVersion: the service rejects the whole journey without them.
  • The customer sees a waiting screen and a button. The button polls GET /onboardings/{id}/steps/{stepId}/status. Submitting a payload to the step is rejected outright.
  • The reply's actionCode decides what happens next — 00 proceed (the reply carries the next step and its form), 80 retry (the service reverts the earlier capture steps and sends the customer back to re-capture), 99 kill (the journey ends). No action code yet means the check is still running and the client keeps polling.
  • None of that behaviour is configurable here. Which upstream check runs, and where a retry sends the customer, live in the service. What you control is where the step sits in the journey, which form backs it, and its wording keys. The API response mock page shows the replies for each action code, and the Journey preview lets you walk all three.

Widgets

WidgetRenders as
text · multiline-textSingle or multi-line entry.
selectorDropdown of the options you defined.
checkboxA single tick — consent, confirmation.
phone-numberDial-code picker plus number.
country-pickerCountry list.
document-arrayFile upload with size and type limits.
call-to-action · call-to-action-boxNative capture — ID scan, selfie, fingerprints. Mocked in the preview.
remote-contentA fetched document with accept / decline.

Condition operators

EQUALS, NOT_EQUALS, IN, NOT_IN, GREATER_THAN, LESS_THAN, IS_EMPTY, IS_NOT_EMPTY. Named predicates: isFatcaCountry, isCrsCountry.

Wording key conventions

Dotted and lower-camel: identity.documentNumber.label, address.error.zipCode.minLength, retail_onboarding.tz.step.basic_info.title. Journey keys carry the country segment (.tz.) and are rewritten when you clone; form keys are usually shared across countries.

Where things live in the studio

ScreenAddressPurpose
Catalogue/catalogueBrowse base forms and countries.
New country/new-countryCreate and manage the draft workspace; review & download.
Journey builder/journeys/{CC}Steps, branches, workflow, exit rules, lint.
Form editor/editor/{CC}/{form}Fields, layout, rules, raw JSON, translations + live preview.
Journey preview/preview/{CC}Step-by-step interactive walkthrough.
Country data/country-data/{CC}Account products, derived currencies and privacy notice.
API Response Mock/api-mock/{CC}The payloads client apps receive; copy or download them for the frontend teams.

Glossary

Base formThe shared definition of a form, used by every country.
Overlay / patchThe country's (and optionally channel's) differences from a base form.
JourneyThe ordered set of screens for a country, plus its branches.
BranchA conditional detour that rejoins the main flow.
Exit ruleA condition that ends the journey early.
Channelweb, android, ios or ussd.
Workspace / draftYour in-progress new country, held in this browser until published.
LintThe structural check on a journey; errors block publishing.
Country dataDatabase-backed per-country configuration: account products, the currencies they imply and the privacy notice. Fetched by the client from lookup endpoints.
Account productOne openable account — currency + residency + scheme code, plus what the picker shows.
API response mockThe journey- and form-config payloads a client app receives, rebuilt from your configuration.
↑ Top