> ## 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.

# Journey URL

> Backend token exchange — how to obtain a journey URL and load it in your web app.

This guide explains where the journey URL comes from, how to obtain it server-side, and how to hand it off to the host page. **The credentials needed to mint a journey URL must never reach the browser.** This page walks through the backend pattern that keeps them server-side.

## Overview

The journey URL is the address your host page loads inside the iframe 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`).

```mermaid theme={null}
sequenceDiagram
    participant Browser as Host Page (Browser)
    participant Backend as Your Backend
    participant Core as GBG Go Core SDK
    participant GBG as GBG Platform

    Browser->>Backend: 1. POST /api/journey/start
    Backend->>Core: 2. go.journeys.start({...})
    Core->>GBG: 3. Authenticated API call
    GBG-->>Core: 4. instanceId + URL
    Core-->>Backend: 5. Journey response
    Backend-->>Browser: 6. Journey URL
    Browser->>Browser: 7. <iframe src={journeyUrl}>
```

The browser never calls the Core SDK. Session creation happens server-side; the host page receives the URL through your own API.

## Why server-side only

`@gbg/go-core`'s authentication flow uses a **customer access token** that requires a client ID + client secret to mint. Per the [Core Web Bridge SDK spec](https://gbg.atlassian.net/browse/GGO-14002), exposing those credentials in browser code lets anyone mint sessions against your tenant. The host page must only ever see the journey URL — never the credentials, never the API responses that contain them.

This rules out three otherwise-tempting patterns:

* **Don't `npm install @gbg/go-core` in your frontend bundle.** The bundle ships to every visitor — including the secret if you embed it.
* **Don't proxy `/api/go/*` through to GBG with the secret attached.** That just relocates the leak; the secret is still callable from the browser.
* **Don't hand the customer access token (post-OAuth) to the browser either.** It's also a server-side secret with full tenant authority.

The only thing the browser sees is the **journey URL** — a short-lived, single-use, signed URL that authorizes exactly one journey instance.

## Core SDK setup (backend)

The steps below run on your backend — Node.js, Bun, or Deno. Install the Core SDK, authenticate once, and reuse the resulting client for every journey session your app mints.

### Installation

```bash theme={null}
npm install @gbg/go-core
```

### Authentication

Generate a customer access token using your client credentials. Do this once on backend startup, or per-request — `@gbg/go-core` handles token caching internally.

```typescript theme={null}
import { Go } from "@gbg/go-core";

const go = new Go();

const auth = await go.tokens.generate({
  clientId: process.env.GO_CLIENT_ID!,
  clientSecret: process.env.GO_CLIENT_SECRET!,
  username: process.env.GO_API_USERNAME!,
  password: process.env.GO_API_PASSWORD!,
  grantType: "password",
});
```

Then initialize the SDK with the token:

```typescript theme={null}
const go = new Go({
  customerAccess: auth.accessToken,
  serverIdx: 0, // 0 = EU, 1 = US, 2 = AU
});
```

### Regional servers

| Index | Region | Server                                         |
| ----- | ------ | ---------------------------------------------- |
| 0     | EU     | `https://eu.platform.go.gbgplc.com/v2/captain` |
| 1     | US     | `https://us.platform.go.gbgplc.com/v2/captain` |
| 2     | AU     | `https://au.platform.go.gbgplc.com/v2/captain` |

Select the region matching your deployment using `serverIdx`, or provide a custom `serverURL`.

## Create a journey session (backend)

Call `go.journeys.start()` with a **resource ID** that identifies which journey template to run:

```typescript theme={null}
const journey = await go.journeys.start({
  resourceId: "a4c68509c24789888eb466@latest",
  context: {
    subject: {
      identity: {
        firstName: "John",
        lastNames: ["Doe"],
        dateOfBirth: "1990-01-01",
      },
    },
  },
});
```

Response shape:

```typescript theme={null}
{
  instanceId: "PiIuACmx8Q8R7qPnAkLAqBAT",  // Unique journey instance (16–64 chars)
  instanceUrl?: "https://..."                // Journey URL (present in most responses)
}
```

The `resourceId` uses the format `<id>@<version>` — use `@latest` to always run the current published version of the template.

### Pre-populating context (optional)

You can pre-populate identity, documents, biometrics, and consent records via the `context` field. This skips data-entry steps the journey would otherwise present:

```typescript theme={null}
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",
  },
}
```

Treat anything in `context` as data your backend has already collected and validated. Don't expose this shape to your frontend without your own authentication and validation in front of it.

### Advanced: device tokens

`@gbg/go-core` also exposes `go.devices.add({ instanceId, scope })` for integrations that need a separate connect token per device — typically for native mobile apps that authenticate the device against the journey instance. **For most web iframe integrations, you don't need this** — the `instanceUrl` from `journeys.start` already carries the authorization the iframe needs.

If your use case requires device-level token issuance (multi-device session handoff, server-rendered handoff with subsequent device authentication, etc.), refer to the `@gbg/go-core` documentation for the appropriate device scope. Adding a device the iframe doesn't need adds friction without benefit.

## Return the URL to the browser

Your backend exposes an endpoint that returns the journey URL. Authenticate this endpoint with your own session/auth middleware — anyone who can call it can mint a journey on your tenant's quota.

```typescript theme={null}
import { Go } from "@gbg/go-core";
import express from "express";

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

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

  const journey = await go.journeys.start({
    resourceId: req.body.resourceId ?? "a4c68509c24789888eb466@latest",
    context: {
      subject: {
        identity: req.body.identity, // validate before passing through
        uid: req.user.id,            // tie journey to authenticated user
      },
    },
  });

  res.json({
    journeyUrl: journey.instanceUrl,
    instanceId: journey.instanceId,
  });
});
```

The `requireUserAuth` middleware is your own — it should verify the user's session, rate-limit, and validate the request body. Without it, the endpoint becomes a free journey-minter for anyone who finds the URL.

## Load the URL in the host page

Once your frontend has the URL, pass it to `BridgeHostProvider`:

```tsx theme={null}
import { BridgeHostProvider } from "@gbg/go-bridge-web-react";
import { useState, useEffect } from "react";

export function StartJourneyButton() {
  const [journeyUrl, setJourneyUrl] = useState<string | null>(null);
  const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);

  async function start() {
    const res = await fetch("/api/journey/start", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({}),
    });
    const data = await res.json();
    setJourneyUrl(data.journeyUrl);
  }

  if (!journeyUrl) {
    return <button onClick={start}>Start identity verification</button>;
  }

  return (
    <BridgeHostProvider
      iframe={iframe}
      allowedOrigins={[new URL(journeyUrl).origin]}
      hostVersion="1.0.0"
      capabilities={{
        "camera.document": { supported: true, version: "1.0" },
      }}
    >
      <iframe ref={setIframe} src={journeyUrl} allow="camera" />
    </BridgeHostProvider>
  );
}
```

`new URL(journeyUrl).origin` builds the `allowedOrigins` entry dynamically — useful when the journey origin varies by region or tenant. Audit this if `journeyUrl` could ever be set from user-controlled input.

## Server-side rendering (Next.js)

In Next.js App Router, fetch the URL in a server component and pass it down to a client component:

```tsx theme={null}
// app/journey/page.tsx (server)
import { Go } from "@gbg/go-core";
import { JourneyClient } from "./JourneyClient";

export default async function Page() {
  const go = new Go({
    customerAccess: process.env.GO_CUSTOMER_ACCESS!,
    serverIdx: 0,
  });

  const journey = await go.journeys.start({
    resourceId: "a4c68509c24789888eb466@latest",
    context: { subject: { identity: { /* ... */ } } },
  });

  return <JourneyClient journeyUrl={journey.instanceUrl!} />;
}
```

```tsx theme={null}
// app/journey/JourneyClient.tsx (client)
"use client";

import { BridgeHostProvider } from "@gbg/go-bridge-web-react";
import { useState } from "react";

export function JourneyClient({ journeyUrl }: { journeyUrl: string }) {
  const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);
  return (
    <BridgeHostProvider
      iframe={iframe}
      allowedOrigins={[new URL(journeyUrl).origin]}
      hostVersion="1.0.0"
      capabilities={{ "camera.document": { supported: true, version: "1.0" } }}
    >
      <iframe ref={setIframe} src={journeyUrl} allow="camera" />
    </BridgeHostProvider>
  );
}
```

The Core SDK never reaches the client bundle because it's only imported by the server component. Verify this by checking your bundle for `@gbg/go-core` — if it appears, you've imported it from a client component by mistake.

## Security considerations

* **Never embed `@gbg/go-core` credentials in browser code.** Client secret, customer access token, and API username/password are all server-side secrets.
* **Always use HTTPS** in production. The journey URL is sensitive; transport it over TLS.
* **The journey URL is short-lived and single-use.** Don't cache it in `localStorage`, don't pass it through query strings that get logged, and don't share it across users.
* **Authenticate the endpoint that returns the URL.** Without your own auth in front, anyone can mint journeys on your quota.
* **Validate `context` before passing it to `@gbg/go-core`.** Treat client-supplied identity data as untrusted — schema-validate and sanity-check before forwarding.
* **Audit `allowedOrigins` construction.** If you build it from `new URL(journeyUrl).origin`, ensure `journeyUrl` itself comes from a trusted source — never from user input.

See [Security → Token Exchange and Credential Safety](/docs/go-v2/developer-integration/sdks/web/security#token-exchange-and-credential-safety) for the full hardening pass.

## Next steps

* [Integration Checklist](/docs/go-v2/developer-integration/sdks/web/integration-checklist) — End-to-end setup walkthrough.
* [Embedding](/docs/go-v2/developer-integration/sdks/web/embedding) — React, framework-agnostic, and SSR patterns.
* [Security](/docs/go-v2/developer-integration/sdks/web/security) — Origin allowlist, CSP, transport, and full security model.
