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

# Messaging

> How messages flow between your host page and the journey iframe, plus the bridge event catalogue.

This guide explains how to send events to the journey iframe, handle incoming requests, observe the message flow, and use the standard bridge event catalogue.

## Message types overview

GBGBridge uses three message types, all sharing the same `BridgeMessage` envelope with a `correlationId` for request-response pairing:

| Type         | Direction        | Purpose                                                        |
| ------------ | ---------------- | -------------------------------------------------------------- |
| **Request**  | Iframe → Host    | The journey asks the host to do something. Expects a response. |
| **Response** | Host → Iframe    | The host returns the result of a request.                      |
| **Event**    | Either direction | Asynchronous notification. No response expected.               |

See [API Reference → BridgeMessage](/docs/go-v2/developer-integration/sdks/web/api-reference#bridgemessage) for the full envelope shape.

## Sending events to the journey

Events are fire-and-forget messages from the host to the journey. Use them to notify the journey of state changes, user actions, or lifecycle transitions.

```typescript theme={null}
// React: get the host via useBridgeHost
const host = useBridgeHost();
host?.sendEvent("host.ready", { timestamp: Date.now() });

// Direct: with a BridgeHost instance
host.sendEvent("host.ready", { timestamp: Date.now() });
```

The SDK generates a fresh correlation ID for every event in the format `web-event-{uuid}`.

### Common host → iframe events

These are events the host can emit to coordinate with the journey. The journey-side SDK subscribes to them.

| Event Action            | When to send                                    | Example data          |
| ----------------------- | ----------------------------------------------- | --------------------- |
| `host.ready`            | After host initialization completes             | `{ timestamp }`       |
| `host.theme.update`     | User changed app theme; journey should re-style | `{ theme: "dark" }`   |
| `host.locale.update`    | User changed locale; journey should re-localize | `{ locale: "en-GB" }` |
| `host.navigation.back`  | User pressed a "back" UI element in the host    | `{}`                  |
| `host.navigation.close` | User pressed "close" in the host                | `{}`                  |

The constants for these are exported as `BridgeActions.HOST_THEME_UPDATE`, `BridgeActions.HOST_NAVIGATION_BACK`, etc. Use them rather than magic strings.

```typescript theme={null}
import { BridgeActions } from "@gbg/go-bridge-web";

host.sendEvent(BridgeActions.HOST_THEME_UPDATE, { theme: "dark" });
```

## Bridge event catalogue (iframe → host)

The journey emits these events. Subscribe via `useBridgeHostEvent` (React) or `delegate.onMessage` (direct).

### Journey lifecycle

| Action                         | Constant                                     | When it fires                                                                            |
| ------------------------------ | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `journey.started`              | `BridgeActions.JOURNEY_STARTED`              | The journey has loaded and is ready for user input.                                      |
| `journey.page.changed`         | `BridgeActions.JOURNEY_PAGE_CHANGED`         | The journey navigated between steps.                                                     |
| `journey.completed`            | `BridgeActions.JOURNEY_COMPLETED`            | The user completed all required steps successfully.                                      |
| `journey.failed`               | `BridgeActions.JOURNEY_FAILED`               | The journey ended in a failure state (verification failed, max attempts exceeded, etc.). |
| `journey.abandoned`            | `BridgeActions.JOURNEY_ABANDONED`            | The user left without completing.                                                        |
| `journey.error`                | `BridgeActions.JOURNEY_ERROR`                | An unrecoverable error occurred mid-journey.                                             |
| `journey.submission.started`   | `BridgeActions.JOURNEY_SUBMISSION_STARTED`   | The journey began submitting collected data.                                             |
| `journey.submission.completed` | `BridgeActions.JOURNEY_SUBMISSION_COMPLETED` | Submission succeeded.                                                                    |
| `journey.submission.failed`    | `BridgeActions.JOURNEY_SUBMISSION_FAILED`    | Submission failed (network or backend error).                                            |

### Capture lifecycle

| Action              | Constant                          | When it fires                                                                                 |
| ------------------- | --------------------------------- | --------------------------------------------------------------------------------------------- |
| `capture.started`   | `BridgeActions.CAPTURE_STARTED`   | A capture step began (the iframe is presenting its capture UI, or has delegated to the host). |
| `capture.completed` | `BridgeActions.CAPTURE_COMPLETED` | A capture step completed successfully.                                                        |
| `capture.failed`    | `BridgeActions.CAPTURE_FAILED`    | A capture step failed.                                                                        |
| `capture.retried`   | `BridgeActions.CAPTURE_RETRIED`   | The user retried after a failed capture.                                                      |

For the canonical payload shapes of each event, refer to the iframe-side SDK ([TypeScript Bridge SDK](/docs/go-v2/developer-integration/sdks/typescript-sdk)) — it is the source of truth for what data each event carries.

### Subscribing in React

```tsx theme={null}
import { useBridgeHostEvent } from "@gbg/go-bridge-web-react";
import { BridgeActions } from "@gbg/go-bridge-web";
import { useNavigate } from "react-router-dom";

function JourneyEventListeners() {
  const navigate = useNavigate();

  useBridgeHostEvent(BridgeActions.JOURNEY_COMPLETED, (data) => {
    navigate(`/success?journey=${data.journeyId}`);
  });

  useBridgeHostEvent(BridgeActions.JOURNEY_FAILED, (data) => {
    navigate(`/failure?reason=${data.reason}`);
  });

  return null;
}
```

### Subscribing without React

```typescript theme={null}
host.delegate = {
  onMessage(_host, message) {
    if (message.type !== "event") return;
    const { action, data } = message.payload as { action: string; data: Record<string, unknown> };
    if (action === BridgeActions.JOURNEY_COMPLETED) {
      window.location.href = `/success?journey=${data.journeyId}`;
    }
  },
};
```

## Handling incoming requests

When the journey sends a request, the host routes it to a registered handler or — if none is registered — to `pendingRequests` plus `delegate.onUnhandledRequest`.

### Using useBridgeCapability (React)

```tsx theme={null}
import { useBridgeCapability } from "@gbg/go-bridge-web-react";

function DocumentCaptureHandler() {
  useBridgeCapability("camera.document.capture", {
    handle: async (request, responder) => {
      const data = (request.payload as { data: Record<string, unknown> }).data;
      const side = data.side as string;
      const image = await captureDocument(side);
      responder.success({ image });
    },
  });
  return null;
}
```

The hook registers on mount and unregisters on unmount. Setting a handler does **not** declare the capability — pass an entry in the `capabilities` prop on the provider, or use `host.registerCustomCapability(...)` to do both in one call. See [Concepts → Declaring vs. Registering](/docs/go-v2/developer-integration/sdks/web/concepts#declaring-vs-registering-capabilities).

### Using registerHandler / registerCustomCapability directly

```typescript theme={null}
// Register only — capability must be declared separately via constructor option
host.registerHandler("camera.document.capture", {
  handle: async (_request, responder) => {
    const image = await captureDocument();
    responder.success({ image });
  },
});

// Declare + register in one call
host.registerCustomCapability("nfc.read", "1.0", {
  handle: async (_request, responder) => {
    responder.unsupported("Not available on this host");
  },
});
```

### Handling via the delegate (catch-all)

Use `delegate.onUnhandledRequest` for catch-all logic — for example, when you have a single coordinator object handling many request types.

```typescript theme={null}
host.delegate = {
  onUnhandledRequest(host, request) {
    const { action } = request.payload as { action: string };
    switch (action) {
      case "host.theme.preference":
        host.respond({
          correlationId: request.correlationId,
          status: "success",
          data: { theme: getPreferredTheme() },
        });
        break;
      default:
        host.respond({
          correlationId: request.correlationId,
          status: "unsupported",
          error: {
            code: "UNSUPPORTED",
            message: `Action '${action}' is not supported`,
            recoverable: false,
          },
        });
    }
  },
};
```

When responding via `host.respond({ ... })`, the action is resolved from the matching `pendingRequests` entry if not explicitly provided.

### Responding to pending requests manually

If a request arrives with no registered handler, it's stored in `pendingRequests` (capped at 50; oldest dropped on overflow). You can respond to it later — for example, after a user interaction.

```tsx theme={null}
function PendingApprovals() {
  const host = useBridgeHost();
  if (!host) return null;

  return (
    <ul>
      {host.pendingRequests.map((req) => {
        const action = (req.payload as { action: string }).action;
        return (
          <li key={req.correlationId}>
            {action}
            <button
              onClick={() =>
                host.respond({
                  correlationId: req.correlationId,
                  status: "success",
                  data: { approved: true },
                })
              }
            >
              Approve
            </button>
            <button
              onClick={() =>
                host.respond({
                  correlationId: req.correlationId,
                  status: "cancelled",
                })
              }
            >
              Deny
            </button>
          </li>
        );
      })}
    </ul>
  );
}
```

`host.pendingRequests` mutates in place rather than emitting React updates. If you need the list to be reactive, mirror it into local state via `useBridgeHostEvent` or `delegate.onUnhandledRequest`.

## Response patterns

`BridgeResponder` exposes four terminal methods (`success`, `error`, `cancelled`, `unsupported`) plus one interim method (`acknowledge`). Call exactly one terminal method per request — subsequent terminal calls on the same responder are no-ops.

### Success with data

```typescript theme={null}
responder.success({
  image: capturedImageBase64,
  width: 1920,
  height: 1080,
});
```

### Error with details

```typescript theme={null}
import { BridgeErrorCode } from "@gbg/go-bridge-web";

responder.error({
  code: BridgeErrorCode.PERMISSION_DENIED,
  message: "Camera permission was denied",
  recoverable: true,
});
```

### User cancellation

```typescript theme={null}
responder.cancelled("User dismissed the capture dialog");
```

The optional `reason` becomes the `error.message` field in the response (with `code: "CANCELLED"`, `recoverable: true`).

### Unsupported action

```typescript theme={null}
responder.unsupported("NFC is not available on web hosts");
```

Same shape as `cancelled`, but with `code: "UNSUPPORTED"` and `recoverable: false`.

### Acknowledged (async processing)

For long-running operations, send an interim `acknowledged` response immediately. The journey treats acknowledgment as "host received but slow" and waits for the terminal response.

```typescript theme={null}
async (request, responder) => {
  responder.acknowledge();

  const result = await longRunningCapture(); // takes 5-10 seconds

  if (result.cancelled) {
    responder.cancelled();
  } else {
    responder.success({ image: result.imageBase64 });
  }
};
```

`acknowledge` is chainable and returns the responder — but calling it after a terminal status is a no-op.

## Observing all messages

Use `delegate.onMessage` to see every validated inbound message (the delegate fires after origin and envelope checks pass).

```typescript theme={null}
host.delegate = {
  onMessage(_host, message) {
    console.log(
      `[Bridge] ${message.type}: ${(message.payload as { action: string }).action}`,
      message,
    );
  },
};
```

For React without setting a delegate, subscribe to specific actions via `useBridgeHostEvent` (events only) — the SDK does not currently expose a hook for "every message."

To inspect the full message buffer, read `host.receivedMessages` (capped at 200; oldest trimmed on overflow). The buffer mutates in place — it does not trigger React updates.

## Error handling

Errors from inside the SDK or your handlers are routed to two places: `host.lastError` (a single-string snapshot) and the delegate's `onError` callback (every error).

### Sources of errors

* **Handler exceptions** — A registered handler throws or returns a rejecting Promise. The error is routed to `onError`, and if the handler hadn't responded, an automatic `error` response with `code: "HANDLER_FAILURE"` and `recoverable: false` is sent so the journey isn't left waiting.
* **Outbound send failures** — Resolving `targetOrigin` failed, the iframe's `contentWindow` is null, or `postMessage` threw. The send is dropped with a `console.warn`; `lastError` is set.
* **Delegate exceptions** — Your `onMessage`, `onUnhandledRequest`, or `onMessageSent` callback threw. Routed to `onError`. A throw from `onError` itself is logged via `console.warn` and swallowed to prevent recursion.

### Subscribing to errors in React

```tsx theme={null}
import { useBridgeHostError } from "@gbg/go-bridge-web-react";

function BridgeErrorBoundary({ children }) {
  const [error, setError] = useState<Error | null>(null);
  useBridgeHostError(setError);

  if (error) {
    return <div role="alert">Bridge error: {error.message}</div>;
  }
  return children;
}
```

### Subscribing without React

```typescript theme={null}
host.delegate = {
  ...host.delegate,
  onError(_host, error) {
    reportToTelemetry({ event: "bridge.error", error: error.message });
  },
};
```

`host.lastError` is a snapshot of the most recent error string — useful for debugging but not for telemetry (you'd miss intermediate errors). Use `delegate.onError` for every-error visibility.

## Next steps

* [Capability Handling](/docs/go-v2/developer-integration/sdks/web/capability-handling) — Declare capabilities, register handlers, manage dynamic state.
* [Embedding](/docs/go-v2/developer-integration/sdks/web/embedding) — React, framework-agnostic, and SSR integration patterns.
* [API Reference](/docs/go-v2/developer-integration/sdks/web/api-reference) — Detailed method signatures and parameters.
