> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinkbill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser Pay platform setup

> Configure a multi-user platform for Browser Pay without duplicating the payment implementation guide.

This page covers the platform layer around Browser Pay: credentials, per-user OAuth connections, tenant isolation, and deployment checks. Browser Pay can coexist with Vault SDK in the same platform; route a checkout to one path before the payment attempt starts. It does not repeat the payment state machine. For the checkout flow, credential handling, result reporting, and recovery, use the [Browser Pay guide](https://docs.clinkbill.com/guides/agent/browser_pay).

> [!NOTE]
>
> A platform has two OAuth concerns. Its server uses the platform credential pair to manage user connections; each user then authorizes a separate MCP connection with Authorization Code + PKCE. Clink may register the same `client_id` for both roles, but the `client_secret` never goes through the user authorization flow.

## Prepare the platform integration

Ask Clink to provide or confirm the following for each environment:

| Item | Platform requirement |
| - | - |
| Partner credentials | `client_id` and `client_secret` for the server-side platform token. Keep the secret on the server. |
| User OAuth client | A public OAuth client for Authorization Code + PKCE. Confirm whether its `client_id` is the partner `client_id` or a separately registered value. |
| MCP resource | The MCP URL and matching OAuth resource for the same environment. |
| Callback | A fixed HTTPS `redirect_uri` registered for the user connection. |
| Credential encryption | An RSA public key in PEM X.509 SubjectPublicKeyInfo format (`BEGIN PUBLIC KEY`) when Clink returns encrypted credentials. Register it separately from OAuth client configuration. |

For the user OAuth client, choose CIMD, pre-registration, or DCR as described in [MCP connection and OAuth](https://docs.clinkbill.com/guides/agent/mcp_integration.md). OAuth client registration and RSA public-key registration are separate steps.

Browser Pay normally needs these user scopes:

```text theme={null}
wallet:read payment:execute instruction:read instruction:write events:read events:consume offline_access
```

Add `wallet:setup` when the platform changes a user's default card. The registered OAuth client must allow every scope requested by the authorization flow. Scope details and tool-specific requirements belong in the [Browser Pay guide](https://docs.clinkbill.com/guides/agent/browser_pay) and [Browser Pay API reference](https://docs.clinkbill.com/api-reference/mcp/browser_pay.md).

## Establish each user connection

Keep the platform token and user token in separate stores. The platform token calls the partner connection endpoints; the user token represents one user when the platform calls MCP.

The platform connection has two phases: the server creates or manages the Clink session, then the user completes Authorization Code + PKCE. Keep the callback state tied to the platform user before opening the MCP connection.

```mermaid theme={null}
%%{init: {"theme":"base","themeVariables":{"fontFamily":"inherit","actorBkg":"#F7F7F7","actorBorder":"#E7E7E7","actorTextColor":"#161616","actorLineColor":"#E7E7E7","signalColor":"#71717A","signalTextColor":"#3F3F46","noteBkgColor":"#F8E1CC","noteBorderColor":"#F8E1CC","noteTextColor":"#161616"},"sequence":{"width":116,"height":44,"actorMargin":64,"diagramMarginX":12,"diagramMarginY":12,"messageMargin":28,"noteMargin":14,"boxMargin":12,"wrap":false,"mirrorActors":false,"actorFontSize":14,"messageFontSize":14,"noteFontSize":14}}}%%
sequenceDiagram
  participant P as Platform server
  participant C as Clink platform API
  participant U as User browser
  participant O as Clink OAuth server
  participant M as Clink MCP
  P->>C: client_credentials
  C-->>P: Platform access token
  P->>C: POST /platform/sessions
  C-->>P: authorized or authorization_required
  alt User authorization required
    P-->>U: Show authorization_url
    U->>O: Authorize with PKCE
    O-->>U: Redirect with code, state, and iss
    U-->>P: OAuth callback
    P->>O: Exchange code + code_verifier
    O-->>P: User access token + refresh token
  end
  P->>M: Connect with the user access token
  M-->>P: Tools and wallet state
```

### Get a platform token

Use the partner credentials with `client_credentials`:

```http theme={null}
POST {API_BASE_URL}/agent/cwallet/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={PLATFORM_CLIENT_ID}
&client_secret={PLATFORM_CLIENT_SECRET}
&scope=agent-platform:account
```

Cache `access_token` until `expires_in` and obtain a new one after expiry. This token manages platform sessions; it cannot call a user's MCP tools directly.

### Create or resume a user session

When a user enables Clink, generate a random `state` and PKCE `code_verifier`, calculate the S256 `code_challenge`, and bind them temporarily to that platform user. Use a verified user email and your stable `external_user_id`:

```http theme={null}
POST {API_BASE_URL}/agent/cwallet/platform/sessions
Authorization: Bearer {PLATFORM_ACCESS_TOKEN}
Content-Type: application/json
```

```json theme={null}
{
  "email": "user@example.com",
  "external_user_id": "platform-user-123",
  "redirect_uri": "https://agent.example.com/oauth/clink/callback",
  "state": "{RANDOM_STATE}",
  "code_challenge": "{S256_CODE_CHALLENGE}",
  "code_challenge_method": "S256"
}
```

Read `data.status` from the response. Clink must confirm the formal `data` schema and field return conditions during enablement:

| status | Platform action |
| - | - |
| `authorized` | Save `customer_id`, the user `access_token`, its expiry, and the returned `refresh_token`. If no new refresh token is returned, keep the existing valid value. |
| `authorization_required` | Save and show `authorization_url`; keep `state` and `code_verifier` until the callback completes. |

When the callback returns `code`, `state`, and `iss`, verify that the state belongs to the user who started the connection and that `iss` matches the issuer in the Authorization Server Metadata for the same environment. Do not persist a cancelled or failed connection.

Exchange the code at the `token_endpoint` from Authorization Server Metadata as a public client. Do not send a `client_secret`:

```http theme={null}
POST {TOKEN_ENDPOINT}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id={OAUTH_CLIENT_ID}
&code={AUTHORIZATION_CODE}
&redirect_uri={REGISTERED_REDIRECT_URI}
&code_verifier={SAVED_CODE_VERIFIER}
&resource={MCP_RESOURCE}
```

`redirect_uri` must exactly match the registered and authorized value. Read `token_endpoint` and the issuer from `{API_BASE_URL}/.well-known/oauth-authorization-server` instead of hardcoding them, and never mix UAT and Production. The `resource` and MCP URL must belong to the same environment.

## Persist platform state safely

Use one record per platform user, environment, and OAuth client. Keep the association with `external_user_id` in the platform database; Clink's `customer_id` is not a substitute for the platform user ID.

| Data | Storage and purpose |
| - | - |
| `external_user_id` | Stable platform key used to find the user's connection. |
| `customer_id` | Clink customer association for this environment and user. |
| User `access_token`, `refresh_token`, expiry | Encrypted server-side credentials. Never share between users or environments. |
| `state`, `code_verifier` | Short-lived callback state. Delete after a successful or failed callback. |
| Checkout facts, `operation_id`, payment identifiers | Recovery context for the same purchase; use the [Browser Pay guide](https://docs.clinkbill.com/guides/agent/browser_pay) for their lifecycle. |

Refresh each user's token serially, including across server instances. Each refresh returns a new `refresh_token` and retires the old one: store the new value before using the new Access Token, and never send a retired token again. If a retired token is sent again, Clink revokes every Refresh Token from that authorization and the user must authorize again. If refresh fails, stop new MCP work for that user and restart authorization. See [Refresh tokens and recover](https://docs.clinkbill.com/guides/agent/mcp_integration.md#refresh-tokens-and-recover).

When a user disconnects, stop new work, revoke the Refresh Token with the configured OAuth client at `/agent/cwallet/oauth/revoke`, and remove local credentials. A user with an empty wallet is still connected; a readable wallet or default card does not authorize a purchase.

Connect MCP with the user's token and read the current tool list and `clink://skill/rules`. If the Host cannot read resources, call `get_skill_manifest` with `include_rules: true`. The platform should make a read-only `get_account_state` call before enabling payment actions.

For a Node.js smoke test, use Node.js 20.19 or later and `@modelcontextprotocol/client@2`: connect to the environment's MCP URL with the user's Access Token, list tools, call `get_account_state` with `{}`, and inspect `structuredContent`. Treat `isError: true` as a failed connection. `connected: true` plus a readable wallet proves the connection; `cardCount: 0` means the wallet is empty, not that OAuth failed. Use the platform's normal per-user refresh flow after this check.

> [accordion] Minimal Node.js wallet smoke test
>
> ```bash theme={null}
> npm install @modelcontextprotocol/client@2
> ```
>
> ```javascript theme={null}
> import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
>
> const token = process.env.CLINK_USER_ACCESS_TOKEN;
> if (!token) throw new Error("Set CLINK_USER_ACCESS_TOKEN.");
>
> const client = new Client({ name: "clink-wallet-check", version: "1.0.0" });
> const transport = new StreamableHTTPClientTransport(
>   new URL(process.env.CLINK_MCP_URL ?? "https://uat-api.clinkbill.com/mcp"),
>   { authProvider: { token: async () => token } },
> );
>
> try {
>   await client.connect(transport);
>   const { tools } = await client.listTools();
>   if (!tools.some(tool => tool.name === "get_account_state")) {
>     throw new Error("get_account_state is unavailable.");
>   }
>   const result = await client.callTool({ name: "get_account_state", arguments: {} });
>   if (result.isError) throw new Error("Wallet read failed.");
>   const account = result.structuredContent;
>   if (!account || !Array.isArray(account.paymentMethods)) {
>     throw new Error("Unexpected wallet response.");
>   }
>   console.log({ connected: true, walletReadable: true, cardCount: account.paymentMethods.length });
> } finally {
>   try {
>     await transport.terminateSession();
>   } finally {
>     await client.close();
>   }
> }
> ```

OAuth grants access to Clink tools; it does not authorize a purchase. A purchase instruction or a one-time user action defines what the Agent may spend. Passkey and other user actions returned by Browser Pay must be presented to the correct user and resumed through the same Operation.

## Hand off a checkout to Browser Pay

The platform remains responsible for the merchant browser and the user experience. The canonical [Browser Pay guide](https://docs.clinkbill.com/guides/agent/browser_pay) defines the payment flow; the platform adapter must provide these inputs and handoffs:

* Read the merchant's final amount, currency, merchant details, and checkout facts. Use minor currency units and preserve the same facts when resuming an Operation.
* Refresh the wallet before choosing a specific `paymentInstrumentId`; let the user confirm the card and use only a card marked `agent_pay_support: true`.
* Deliver `next_action.params.redirect_url` and QR or user-device actions unchanged. Never open a `USER_DEVICE_ONLY` link in the Agent's browser.
* Preserve the issuing Operation and payment identifiers before filling the merchant form. Keep encrypted credentials and decrypted card fields out of model input, logs, and ordinary records.
* Report the result observed on the merchant page with `confirm_result`. A Clink report or PSP response does not replace merchant confirmation.

For amount checks, credential decryption, `next_action` handling, result enums, and duplicate-payment recovery, continue to the [Browser Pay guide](https://docs.clinkbill.com/guides/agent/browser_pay). For card and purchase authorization management, use [Wallet Service](https://docs.clinkbill.com/guides/agent/wallet_service.md).

## Validate before rollout

Run these checks in Sandbox UAT and repeat the relevant checks in Production:

* Two platform users can share an OAuth client while seeing only their own wallet, Operations, and payment context.
* An already authorized user can obtain a credential; a user action can be presented and resumed through the same Operation.
* The registered RSA public key decrypts a credential in the trusted platform component, while the secret key and card fields stay out of the Agent and logs.
* Access Token expiry, Refresh Token rotation, callback replay, user disconnect, and authorization failure have explicit handling.
* Success, failure, unsubmitted, and unknown merchant outcomes are reported with the correct Clink identifiers and never trigger an automatic duplicate payment.

## Confirm with Clink

Before production, confirm the `/platform/sessions` `data` schema, the relationship between partner and public OAuth `client_id`, the OAuth scope registration, the environment resource values, and the public-key registration process. Keep these values in deployment configuration rather than inferring them from examples.

## Related docs

* [Browser Pay guide](https://docs.clinkbill.com/guides/agent/browser_pay): payment mechanics, credential protection, reporting, and recovery.
* [MCP connection and OAuth](https://docs.clinkbill.com/guides/agent/mcp_integration.md): protocol connection details.
* [Browser Pay API reference](https://docs.clinkbill.com/api-reference/mcp/browser_pay.md): tool fields and response shapes.
* [Operations and recovery](https://docs.clinkbill.com/api-reference/mcp/operations.md): Operation states and recovery rules.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.