Skip to main content
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.

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.
Once verification is complete, you can view journey details and outcomes in the Investigation portal.

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:
  1. Add the document identity and age verification modules to the journey, as shown in the image below:
doc and age module img
The journey also includes evaluation steps that route the flow based on module outcomes.
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.
For instructions on how to add modules to a journey, refer to Configure modules.
  1. 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.
  1. Navigate to Dashboard in the Journey builder to get your resource ID and version for integrating the journey with the API.
Next, set up the development environment.

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
These are default outcomes and can be modified as needed. For the purpose of this tutorial, leave the default outcomes as-is and use them to set up evaluation decisions in the journey builder. For more information on configuring module outcomes, refer to How to configure module outcomes in GO.

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:
  1. Click the + icon at the bottom of the last module in your journey.
  2. Navigate to Routing > Evaluation.
  3. Click Add to journey. This step adds the evaluation node to your journey.
  4. Click the evaluation node. The right sidebar shows the evaluation configuration options.
  5. 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
doc and age module img

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

Install lucide-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
There are three key parts to this file:
  • @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 like bg-gbg-700 and text-gbg-500 available throughout the app. The --font-sans and --font-display variables set up custom font families for body text and headings.
The 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 of src/app/layout.tsx with the following. This imports the global styles and sets up the page metadata:
Typescript
The 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 client credentials and journey details. You create API clients in the Account management portal. For more information, see Manage API clients.

Architecture overview

Before writing code, here’s how the app is structured. Understanding the architecture helps you see how each piece fits together.
The backend API routes act as a secure proxy between the browser and the GBG GO API. The frontend never sees your credentials. On the frontend, the domain element registry is the key architectural concept. It maps API card IDs to field definitions, so the form builds itself from the interaction data.

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 an instanceId 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. Create src/app/api/auth/token.ts to handle OAuth 2.0 token retrieval with in-memory caching:
Typescript
This function uses the OAuth 2.0 client credentials grant to exchange your API client credentials for an access token. The token is cached in memory with a 60-second buffer before expiry, so subsequent API calls reuse the same token without hitting the auth server on every request.

Create the start journey route

Create src/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 example abc123...@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 a documentImage is provided, it is included as a prefilled document using the side1Image field. 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
The response returns an 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

Create src/app/api/journey/fetch/route.ts:
Typescript
This route sends the 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 are required and which are optional.
  • outstanding: Domain elements that still need to be collected.
Because the platform might need a moment to prepare the interaction after the journey starts, the frontend polls this endpoint until data is available.

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

Create src/app/api/journey/submit/route.ts:
Typescript
This route handles the complete submission payload and forwards it to the GBG journey/interaction/submit endpoint. There are two important things this route does:
  • Document handling: The frontend sends the document image as a private _documentImage field. The route strips this field from the payload and reformats it into the structure the GBG API expects: context.subject.documents[] with side1Image (the base64 image), side2Image (empty string), and type set 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 VerificationForm component:
    • instanceId and interactionId: 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

Create src/app/api/journey/state/route.ts:
Typescript
The frontend polls this endpoint every 2 seconds after submission. Once 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 as FullName, 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

Create src/lib/types.ts to define the interfaces used across the app. These types model the GBG API responses and the dynamic form system:
Typescript
The key Typescript types to understand are:
  • 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 flat Record<string, string> that stores all form field values keyed by field ID. This simplifies state management in the form components.

Client-side API layer

Create src/lib/api.ts to provide typed functions that the frontend components call.
Typescript
The 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. Create src/lib/domain-elements.ts:
Typescript
Each domain element definition has three key parts:
  • fields: An array of FieldDef objects that describe the form inputs, their types, labels, placeholders, validation rules, and grid layout hints. For example, colSpan: 1 renders the field at half-width in a two-column grid.
  • requiredFields: The field IDs that must be filled when the domain element’s collects spec is "required". Fields marked alwaysOptional: true such 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, the FullName element splits comma-separated last names into an array: "Doe, Smith" becomes ["Doe", "Smith"].
The 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

Create src/lib/domain-mapper.ts. This module transforms the flat FormValues map into the structured SubjectData payload that the GBG API expects:
Typescript
The 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

Create src/lib/validation.ts. This module validates a single page of form values against the domain element definitions:
Typescript
Validation is driven entirely by the registry. For each domain element on the current page, the function checks whether empty required fields are missing and runs any custom 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

Create src/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 using FileReader and stores the result as a string in FormValues. After upload, it shows a confirmation state with a clear button.
  • Select inputs: Renders a <select> dropdown populated from the options array defined in the field’s FieldDef.
  • Standard inputs: Renders <input> elements for text, email, tel, and date types. Date fields automatically set max to 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

Create src/components/CardRenderer.tsx. This component takes a single card from the interaction response and renders the appropriate form fields:
Typescript
The CardRenderer is the bridge between the API response and the form UI. Here’s its rendering logic:
  • Skip navigation cards: ControlCard is a navigation-only card with no fields to collect, so it returns null.
  • Resolve elements: It calls resolveCardElements with 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 with colSpan: 1 take half the width, while fields with colSpan: 2 (or no colSpan) 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 collects spec from the API
    • The element’s requiredFields list
    • The field’s alwaysOptional flag

StepIndicator component

Create src/components/StepIndicator.tsx. This component renders a progress bar showing which page the user is on:
Typescript
The 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

Create src/components/VerificationForm.tsx. This is the main orchestrator that manages the entire verification workflow:
Typescript
The VerificationForm component manages the entire verification lifecycle as a state machine with six stages: The 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, create src/app/page.tsx to render the VerificationForm with the journey resource ID from your environment variables:
Typescript
This is a server component that reads the GBG_JOURNEY_RESOURCE_ID environment variable and passes it as a prop to the client-side VerificationForm.

Demo

Run the development server:
Bash
Navigate to localhost:3000. You should see the application running:
Demo app Pn
To test the verification flow:
  1. Click Begin verification. The app starts the journey and loads the form pages.
  2. 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.
  3. Click Submit on the final page. The app submits the data and polls for the result.
  4. Investigate the details of this session in the Investigation portal.
Investigate Pn
In the screenshot above, you can see that the decision is “Accept.” This means the uploaded document was successfully extracted and classified by GO modules, and the user’s age meets the legal requirement. The Investigation portal session details page shows the uploaded document image along with the extracted data fields. The full raw API response is also available for debugging.

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 PrimaryDocument domain element requires document data at context.subject.documents[] with three specific fields:
Json
The submit route in this tutorial handles this formatting automatically via the _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.