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:
For the user OAuth client, choose CIMD, pre-registration, or DCR as described in MCP connection and OAuth. OAuth client registration and RSA public-key registration are separate steps.
Browser Pay normally needs these user scopes:
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 and Browser Pay API reference.
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.Get a platform token
Use the partner credentials withclient_credentials:
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 randomstate 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:
data.status from the response. Clink must confirm the formal data schema and field return conditions during enablement:
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:
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 withexternal_user_id in the platform database; Clink’s customer_id is not a substitute for the platform user ID.
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.
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.
Minimal Node.js wallet smoke test
Minimal Node.js wallet smoke test
Hand off a checkout to Browser Pay
The platform remains responsible for the merchant browser and the user experience. The canonical Browser Pay guide 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 markedagent_pay_support: true. - Deliver
next_action.params.redirect_urland QR or user-device actions unchanged. Never open aUSER_DEVICE_ONLYlink 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.
next_action handling, result enums, and duplicate-payment recovery, continue to the Browser Pay guide. For card and purchase authorization management, use Wallet Service.
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: payment mechanics, credential protection, reporting, and recovery.
- MCP connection and OAuth: protocol connection details.
- Browser Pay API reference: tool fields and response shapes.
- Operations and recovery: Operation states and recovery rules.