In this tutorial, you’ll use the GBG GO Journey API v2 to build an identity and age verification application that dynamically renders form fields based on your journey configuration. Instead of hardcoding forms, the app fetches interaction data from the API and updates the UI at runtime, so when you add or remove module elements in the Journey builder, the frontend updates automatically.Documentation Index
Fetch the complete documentation index at: https://docs.go.gbgplc.com/llms.txt
Use this file to discover all available pages before exploring further.
What this tutorial covers
By the end, you’ll have a fully functional demo application that:- Starts a verification journey via the GO API.
- Fetches interaction data to dynamically render form pages and fields.
- Collects identity information (name, date of birth, address, documents) and submits it for verification.
- Polls for journey results and displays the outcome.
Prerequisites
- Access to the GO dashboard with create permissions for adding modules to the journey.
- For developers:
- Node.js v18 or higher installed on your local machine.
- A working knowledge of TypeScript and React.
- GBG GO API credentials. For more details, refer to Quickstart for developers.
Get started
To get started with this tutorial:- Add the document identity and age verification modules to the journey, as shown in the image below:

When adding the modules to your journey, ensure you select the v2 version of each module variant. For example, select Document Classification v2 rather than the original Document Classification variant. The v2 variants are designed to work with the latest API and have improved features and performance.
- Click Publish to Preview.
Modules come with default outcomes, which are used in this tutorial. You can configure different outcomes for each module. To learn more, refer to How to configure module outcomes in GO.
- Navigate to Dashboard in the Journey builder to get your resource ID and version for integrating the journey with the API.
Module outcomes in this tutorial
Before moving forward, it’s important to understand the module outcomes used in this tutorial:-
Document Classification module: The Document Classification module performs a range of checks based on the captured document. Results include document type, issuer, and relevant checks to aid decisions made prior to extraction and field-level authentication.
Below are the default outcomes for the Document Classification module. These outcomes can be configured in the Journey builder based on your use case and business needs.
- Document Classified
- Document NOT Classified
-
Document Extraction module: The Document Extraction module extracts all fields from the previously classified document. You can specify which data fields are important, and the module reports whether each selected field was successfully or unsuccessfully extracted.
Below are the default outcomes for the Document Extraction module. These outcomes can be configured in the Journey builder based on your use case and business needs.
- Extraction Successful
- Extraction Unsuccessful
- ERROR
-
Age Verification module: Age Verification for US.
Below are the default outcomes for the Age Verification module. These outcomes can be configured in the Journey builder based on your use case and business needs.
- Of Age
- Under Age
- Match Restricted
- Can Not Confirm Age
- ERROR
Set up journey decisions
This section guides you through configuring journey decisions based on module outcomes. Follow the steps below to add evaluation decisions to your journey:- Click the + icon at the bottom of the last module in your journey.
- Navigate to Routing > Evaluation.
- Click Add to journey. This step adds the evaluation node to your journey.
- Click the evaluation node. The right sidebar shows the evaluation configuration options.
- Click Configure decisions to open the decision configuration panel. Based on module outcomes, set up the following decisions:
- If Document Classification is not Document Classified → Reject.
- If Document Extraction is Extraction Unsuccessful → Manual review.
- If all modules return successful outcomes → Accept

Set up the development environment
This section walks you through creating the project and installing dependencies.Step 1: Create a new Next.js project
Open your terminal and run the following commands to scaffold a Next.js project with TypeScript and Tailwind CSS:Bash
Step 2: Install dependencies
Installlucide-react for icons and @tailwindcss/forms for styled form elements:
Bash
Step 3: Configure Tailwind CSS and add global styles
Define custom colours, fonts, and plugins directly in your CSS file using@theme and @plugin directives.
Replace the contents of src/app/globals.css with the following:
Css
@import "tailwindcss": Loads all of Tailwind’s base, component, and utility styles.@plugin "@tailwindcss/forms": Registers the forms plugin, which provides styled reset styles for form elements like inputs and selects.@theme { ... }: Defines custom design tokens. The--color-gbg-*variables create the GBG brand colour palette, making utility classes likebg-gbg-700andtext-gbg-500available throughout the app. The--font-sansand--font-displayvariables set up custom font families for body text and headings.
input-field, input-label, btn-primary, and btn-secondary classes in the @layer components block are reusable component styles used throughout the frontend. Defining them here keeps the component markup clean.
Step 4: Update the root layout
Replace the contents ofsrc/app/layout.tsx with the following. This imports the global styles and sets up the page metadata:
Typescript
globals.css import loads the Google Fonts and Tailwind styles. The metadata export sets the page title and description that appear in the browser tab and search results.
Step 5: Set up environment variables
Create a.env.local file in your project root to store your API credentials:
Variables
Replace the placeholder values with your actual GBG GO API credentials and journey details.
Architecture overview
Before writing code, here’s how the app is structured. Understanding the architecture helps you see how each piece fits together.Start the journey
The first API call in the verification flow is starting a journey. This creates a new journey instance on the GBG platform and returns aninstanceId that you use for subsequent requests.
Create the project folders
Create the directories for the API routes:Bash
Create the authentication token manager
All GBG API calls require a bearer token. Createsrc/app/api/auth/token.ts to handle OAuth 2.0 token retrieval with in-memory caching:
Typescript
Create the start journey route
Createsrc/app/api/journey/start/route.ts. This route receives a resourceId from the frontend and calls the GBG journey/start endpoint.
There are three things to note in the request body:
resourceId: The journey hash and version you copied from the Journey Builder dashboard, for exampleabc123...@latest.context.config.delivery: "api": This tells GBG you are consuming the journey via the API rather than a hosted web flow.context.subject.documents(optional): When adocumentImageis provided, it is included as a prefilled document using theside1Imagefield. This is used later in the submit flow. The document is uploaded at journey start time so the platform can begin processing it early.
Typescript
instanceId, a unique identifier for this journey session. The frontend uses this instanceId to fetch the current interaction and submit user data as they complete the form.
Fetch interactions
After starting a journey, the GBG platform may take a moment to prepare the first interaction. An interaction is the set of form pages, cards, and fields that the platform needs you to collect from the user. You fetch this data and use it to build the form UI dynamically.Create the fetch interaction route
Createsrc/app/api/journey/fetch/route.ts:
Typescript
instanceId to the GBG journey/interaction/fetch endpoint and returns the interaction data. The response includes:
interactionId: Identifies this specific interaction — required when submitting data.interaction.resource.data.pages: An array of pages, each containing cards that map to domain elements (for example,NameCard,DateOfBirthCard,AddressCard).interaction.collects: An array specifying which domain elements arerequiredand which areoptional.outstanding: Domain elements that still need to be collected.
Submit the interaction
When the user completes the final page and clicks Submit, the app needs to send all collected data through the/journey/interaction/submit endpoint for module processing.
Create the submit interaction route
Createsrc/app/api/journey/submit/route.ts:
Typescript
journey/interaction/submit endpoint. There are two important things this route does:
-
Document handling: The frontend sends the document image as a private
_documentImagefield. The route strips this field from the payload and reformats it into the structure the GBG API expects:context.subject.documents[]withside1Image(the base64 image),side2Image(empty string), andtypeset to"Primary". This separation keeps the frontend simple while ensuring the API receives the correct format. -
Payload forwarding: The remaining payload contains three parts assembled by the
VerificationFormcomponent:instanceIdandinteractionId: Identifies which journey instance and interaction this submission belongs to.participants: An array of domain element IDs that were collected, for example[{ domainElementId: "FullName" }, { domainElementId: "PrimaryDocument" }]. This tells the API which elements you are providing data for.context.subject: The structured identity data such as names, date of birth, address, phone, and email fields in the format the API expects.
Fetch journey results
After submitting the interaction, the GBG platform processes the data through the verification modules configured in the journey. This processing happens asynchronously, so the app polls for the final result.Create the journey state route
Createsrc/app/api/journey/state/route.ts:
Typescript
status changes from "InProgress" to "Completed" or "Failed", the VerificationForm transitions to the result screen.
Building the frontend
In this section, you’ll build the components that render the dynamic form based on the interaction data. The key concept is the domain element registry, a mapping of GBG domain element IDs such asFullName, DateOfBirth, Address to field definitions that tell the app how to render them.
Create the frontend folders
Create the directories for the library modules and components:Bash
TypeScript types
Createsrc/lib/types.ts to define the interfaces used across the app. These types model the GBG API responses and the dynamic form system:
Typescript
InteractionFetchResponse: The full response from the fetch interaction endpoint, containing pages, cards, and collection requirements.DomainElementDef: Defines a domain element’s form fields, which are required, and how to transform the collected values into the API submission payload. This is the core of the dynamic form system.FormValues: A flatRecord<string, string>that stores all form field values keyed by field ID. This simplifies state management in the form components.
Client-side API layer
Createsrc/lib/api.ts to provide typed functions that the frontend components call.
Typescript
request helper standardises error handling across all API calls. The pollForInteraction function is particularly important, after starting a journey, the GBG platform might take a few seconds to prepare the interaction. This function retries every 3 seconds for up to 60 seconds, tolerating transient 5xx errors during the startup window.
Domain element registry
The domain element registry is the heart of the dynamic form system. It defines what fields each card collects and how to map those values to the GBG API payload. Createsrc/lib/domain-elements.ts:
Typescript
-
fields: An array ofFieldDefobjects that describe the form inputs, their types, labels, placeholders, validation rules, and grid layout hints. For example,colSpan: 1renders the field at half-width in a two-column grid. -
requiredFields: The field IDs that must be filled when the domain element’scollectsspec is"required". Fields markedalwaysOptional: truesuch as middle names are excluded from this check. -
toSubject: A function that transforms flat form values into the nested structure the GBG API expects. For example, theFullNameelement splits comma-separated last names into an array:"Doe, Smith"becomes["Doe", "Smith"].
CARD_TO_DOMAIN mapping at the bottom connects GBG card IDs like NameCard to domain element IDs like FullName. When the API returns a page with a NameCard, the registry resolves it to the FullName element, which knows exactly which fields to render. To support a new domain element, you add an entry to the registry, with no component changes needed.
Domain mapper
Createsrc/lib/domain-mapper.ts. This module transforms the flat FormValues map into the structured SubjectData payload that the GBG API expects:
Typescript
buildSubjectPayload function iterates over all collected domain elements, calls each element’s toSubject mapper, and merges the results into a single identity object. The return type is Record<string, unknown> rather than a strict SubjectData because the submit route adds document data (documents[]) at the subject level separately. Each domain element only knows about its own fields, but the final payload contains data from all elements across all pages.
Form validation
Createsrc/lib/validation.ts. This module validates a single page of form values against the domain element definitions:
Typescript
validate function defined on the field (for example, the date-of-birth validator that rejects future dates). The collects array from the API response determines whether each element is required or optional.
DynamicField component
Createsrc/components/DynamicField.tsx. This component renders a single form field based on its FieldDef type:
Typescript
DynamicField handles four distinct field types:
- File inputs: Uses a hidden
<input type="file">behind a styled upload button. When a file is selected, it reads the file as a base64 data URI usingFileReaderand stores the result as a string inFormValues. After upload, it shows a confirmation state with a clear button. - Select inputs: Renders a
<select>dropdown populated from theoptionsarray defined in the field’sFieldDef. - Standard inputs: Renders
<input>elements fortext,email,tel, anddatetypes. Date fields automatically setmaxto today’s date to prevent future dates. - Labels and errors: Every field shows a required indicator (
*) or “optional” tag, and displays validation errors and hint text below the input.
CardRenderer component
Createsrc/components/CardRenderer.tsx. This component takes a single card from the interaction response and renders the appropriate form fields:
Typescript
CardRenderer is the bridge between the API response and the form UI. Here’s its rendering logic:
-
Skip navigation cards:
ControlCardis a navigation-only card with no fields to collect, so it returnsnull. -
Resolve elements: It calls
resolveCardElementswith the card ID to look up the matching domain element definitions from the registry. If no match is found, it renders a warning banner. This makes it safe to add new card types in the Journey Builder before updating the frontend. -
Determine layout: If any field in the element has
colSpan: 1, it uses a two-column CSS grid. Fields withcolSpan: 1take half the width, while fields withcolSpan: 2(or nocolSpan) span the full width. This is how the address form renders City and County side by side. -
Determine required state: Each field’s required state is computed from three sources:
- The
collectsspec from the API - The element’s
requiredFieldslist - The field’s
alwaysOptionalflag
- The
StepIndicator component
Createsrc/components/StepIndicator.tsx. This component renders a progress bar showing which page the user is on:
Typescript
StepIndicator renders numbered circles connected by lines. It receives the pages array directly from the interaction response, so the number of steps automatically matches however many pages the journey has. Completed steps show a checkmark icon, the current step has a highlighted ring, and future steps are greyed out.
VerificationForm component
Createsrc/components/VerificationForm.tsx. This is the main orchestrator that manages the entire verification workflow:
Typescript
VerificationForm component manages the entire verification lifecycle as a state machine with six stages:
| Stage | What happens |
|---|---|
idle | Shows a “Begin verification” button. |
starting | Calls startJourney, then polls fetchInteraction until pages are ready. |
form | Renders the multi-page form with StepIndicator and CardRenderer. |
submitting | Starts a new journey, fetches the interaction, builds the API payload using buildSubjectPayload, and submits everything including the document image via the /api/journey/submit route. The document is passed as a private _documentImage field. The server-side route formats it into the structure the GBG API expects (documents[{side1Image, side2Image, type: "Primary"}]). |
polling | Polls fetchJourneyState every 3 seconds (up to 60 attempts) waiting for the journey to complete. |
result | Shows a success, failure, or “still processing” screen with a reset button. |
handleNext function does double duty: on intermediate pages it validates and advances to the next page, and on the last page it triggers the full submission flow. Validation is per-page, only the domain elements present on the current page are validated, so users see errors immediately rather than at the end.
The handleFieldChange callback clears field-level errors as soon as the user starts typing, providing instant feedback.
Entry point page
Finally, createsrc/app/page.tsx to render the VerificationForm with the journey resource ID from your environment variables:
Typescript
GBG_JOURNEY_RESOURCE_ID environment variable and passes it as a prop to the client-side VerificationForm.
Demo
Run the development server:Bash
localhost:3000. You should see the application running:

- Click Begin verification. The app starts the journey and loads the form pages.
- Fill in the identity information across each step. The form fields and pages are designed and generated based on interaction fetch response. If you add or remove domain elements in the Journey builder, the form updates automatically.
- Click Submit on the final page. The app submits the data and polls for the result.
- Investigate the details of this session in the Investigation portal.

Troubleshooting
This section covers common errors you may encounter when integrating with the GBG GO API and how to resolve them.PrimaryDocument data is missing from context
This error occurs when the interaction submit payload doesn’t include document data in the format the API expects.Json
- Fix: The
PrimaryDocumentdomain element requires document data atcontext.subject.documents[]with three specific fields:
Json
_documentImage field.
Missing credentials for modules
This error occurs when a module in your journey requires API credentials or a licence key that hasn’t been configured in the GO platform.Json
- Fix: If you don’t have credentials for that module, either remove it from the journey or contact your GBG account manager to have them provisioned. Re-publish the journey after making changes.