Skip to main content
This guide explains where the journey URL comes from, how to obtain it, and what your Android app needs to do with it.

Overview

The journey URL is the web address your WebView loads to start an identity verification flow. It is not hardcoded β€” your backend generates it at runtime using the GBG Go Core SDK (@gbg/go-core). The Android app never calls the Core SDK directly. Session creation happens server-side, and the app receives the URL through whatever API your backend exposes.

Core SDK setup

Installation

npm install @gbg/go-core

Authentication

The Core SDK authenticates with a customer access token (Bearer). Generate one using the SDK’s token endpoint:
import { Go } from "@gbg/go-core";

const go = new Go();

const auth = await go.tokens.generate({
  clientId: "your-client-id",
  clientSecret: "your-client-secret",
  username: "api-user@example.com",
  password: "your-password",
  grantType: "password",
});

// auth.accessToken is a Bearer token for subsequent calls
Then initialize the SDK with the token:
const go = new Go({
  customerAccess: auth.accessToken,
  serverIdx: 0, // 0 = EU, 1 = US, 2 = AU
});

Regional servers

IndexRegionServer
0EUhttps://eu.platform.go.gbgplc.com/v2/captain
1UShttps://us.platform.go.gbgplc.com/v2/captain
2AUhttps://au.platform.go.gbgplc.com/v2/captain
Select the appropriate region for your deployment using serverIdx, or provide a custom serverURL.

Create a journey session

Journey URL generation is a two-step process.

Step 1: Start the journey

Call go.journeys.start() with a resource ID that identifies which journey template to run:
const journey = await go.journeys.start({
  resourceId: "a4c68509c24789888eb466@latest",
  context: {
    subject: {
      identity: {
        firstName: "John",
        lastNames: ["Doe"],
        dateOfBirth: "1990-01-01",
      },
    },
  },
});
Response:
{
  instanceId: "PiIuACmx8Q8R7qPnAkLAqBAT",  // Unique journey instance (16–64 chars)
  instanceUrl?: "https://..."                 // Journey UI URL (present in some responses)
}
The resourceId uses the format <id>@<version> β€” use @latest to always get the current version of the journey template.

Pre-populate context (optional)

You can pass identity data, documents, biometrics, and consent records in the context field. This pre-populates the journey so the end user doesn’t have to re-enter information you already have:
context: {
  subject: {
    identity: {
      firstName: "John",
      middleNames: ["Robert"],
      lastNames: ["Doe"],
      dateOfBirth: "1990-01-01",
      // ... additional fields as needed
    },
    documents: [],   // Pre-captured document data
    biometrics: [],  // Pre-captured biometric data
    consent: [],     // Consent records
    uid: "your-internal-user-id",
  },
},

Step 2: Get a connect token

For native app integrations, generate a connect token that the device uses for authentication:
const device = await go.devices.add({
  instanceId: journey.instanceId,
  scope: ["mobile"],
});
Response:
{
  connectToken: "s4FUx8Ny8ijXRtFigz3x1_8rb9bd_5ZD",
  tokenType: "connect",
  expiresIn: 120,    // seconds
  scope: ["mobile"],
}
The connectToken is short-lived (typically 120 seconds). Your backend should return it to the Android app immediately after generating it.

Construct the URL

How you get the final URL to load in the WebView depends on your integration pattern:
  • If the instanceUrl is present in the Step 1 response, use it directly.
  • Otherwise, your backend constructs the URL from the instanceId and connectToken according to your deployment’s URL scheme.
In either case, your backend returns the URL to the Android app through your own API.

Load the URL in Android

Once the Android app has the URL from your backend, attach a BridgeHost to a WebView and load it. In Compose:
@Composable
fun JourneyScreen(journeyUrl: String) {
  val host = remember { BridgeHost(hostVersion = "1.0.0") }

  AndroidView(
    factory = { context ->
      WebView(context).also { webView ->
        host.attach(webView)
        webView.loadUrl(journeyUrl)
      }
    },
  )

  DisposableEffect(Unit) {
    onDispose { host.detach() }
  }
}
In the View system the pattern is the same: create (or inflate) a WebView, call host.attach(webView), then webView.loadUrl(journeyUrl). The bridge handles everything else β€” attach() configures the WebView (JavaScript, DOM storage, the bootstrap-injecting WebViewClient) and registers the JavaScript interface, so bootstrap injection and capability negotiation happen automatically when the page loads.

Complete backend example

A minimal Express endpoint that creates a journey session and returns the URL:
import { Go } from "@gbg/go-core";
import express from "express";

const app = express();
app.use(express.json());

app.post("/api/journey/start", async (req, res) => {
  const go = new Go({
    customerAccess: process.env.GO_CUSTOMER_ACCESS,
    serverIdx: 0,
  });

  // Step 1: Start journey
  const journey = await go.journeys.start({
    resourceId: req.body.resourceId ?? "a4c68509c24789888eb466@latest",
    context: {
      subject: {
        identity: req.body.identity,
      },
    },
  });

  // Step 2: Get connect token for the mobile device
  const device = await go.devices.add({
    instanceId: journey.instanceId,
    scope: ["mobile"],
  });

  res.json({
    instanceId: journey.instanceId,
    journeyUrl: journey.instanceUrl,
    connectToken: device.connectToken,
    expiresIn: device.expiresIn,
  });
});
The Android reference app ships a companion server (under server/) that implements exactly this pattern. It runs on port 3000 and exposes POST /api/journey/start (body { resourceId? }) plus GET /api/journey/:instanceId/state for fetching the journey outcome after completion.
And the corresponding Android call, using OkHttp and kotlinx-serialization:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import java.util.concurrent.TimeUnit

object JourneyService {

  @Serializable
  data class StartResponse(
    val journeyUrl: String,
    val instanceId: String,
    val connectToken: String? = null,
    val expiresIn: Int? = null,
  )

  class JourneyServiceException(
    message: String,
    cause: Throwable? = null,
  ) : RuntimeException(message, cause)

  private val json = Json { ignoreUnknownKeys = true }

  private val client = OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build()

  suspend fun startJourney(serverUrl: String, resourceId: String?): StartResponse =
    withContext(Dispatchers.IO) {
      val endpoint = "${serverUrl.trimEnd('/')}/api/journey/start"

      val bodyPayload =
        if (resourceId.isNullOrEmpty()) JsonObject(emptyMap())
        else JsonObject(mapOf("resourceId" to JsonPrimitive(resourceId)))
      val body = bodyPayload.toString().toRequestBody("application/json".toMediaType())

      val request = Request.Builder()
        .url(endpoint)
        .post(body)
        .header("Content-Type", "application/json")
        .build()

      val response = try {
        client.newCall(request).execute()
      } catch (error: IOException) {
        throw JourneyServiceException(
          "Cannot reach the server at $endpoint: ${error.message}. Is it running?",
          error,
        )
      }

      response.use { resp ->
        val raw = resp.body?.string().orEmpty()
        if (!resp.isSuccessful) {
          throw JourneyServiceException("Server returned HTTP ${resp.code}")
        }
        try {
          json.decodeFromString(StartResponse.serializer(), raw)
        } catch (error: Throwable) {
          throw JourneyServiceException("Unexpected server response", error)
        }
      }
    }
}
When developing against a backend on your own machine, remember that the Android emulator routes localhost / 127.0.0.1 to the emulator itself β€” not the host. Use the loopback alias http://10.0.2.2:3000 to reach a server running on the host machine, or run adb reverse tcp:3000 tcp:3000 and keep using 127.0.0.1. Android also blocks cleartext HTTP by default (API 28+), so scope a network_security_config.xml exception to 10.0.2.2/localhost for local development rather than enabling cleartext traffic globally.

Security considerations

  • Never embed the customer access token in the Android app. It is a server-side secret. The Android app only ever sees the journey URL (and optionally the connect token).
  • Always use HTTPS in production.
  • Connect tokens are short-lived (typically 120 seconds) and single-use. Don’t cache or persist them.
  • The journey URL should be treated as sensitive β€” don’t log it or store it beyond the current session.
  • Fetch the URL over an authenticated channel between your app and your backend (e.g., behind your own auth middleware).

Next steps