Prerequisites
Pay with Coinbase requires Payment Acceptance onboarding, where you receive an OAuth client (client_id, client_secret) and register your redirect_uri. Contact our team to get started.
Examples on this page use production hosts. In Sandbox, use these hosts instead:
How it works
1. Link the customer’s Coinbase account
Linking uses OAuth 2.0 with PKCE and returns the access and refresh tokens you need for every payment.Required scopes
Request all nine scopes:
A scope your client isn’t allowed to request returns
invalid_scope. A missing required scope makes authorization fail with 403 forbidden.
Start a linking session
POST https://login.coinbase.com/oauth2/init-oauth-session
Authentication: CDP API key, Authorization: Bearer <JWT> (generate a JWT)
Send the customer to Coinbase
Openlink_url. It opens the Coinbase app when installed (Universal Link on iOS, App Link on Android), and an in-app browser otherwise. After the customer signs in and consents, Coinbase redirects them to your redirect_uri.
Confirm the link
After the redirect, exchange the session for tokens:POST https://login.coinbase.com/oauth2/confirm-link
Authentication: CDP API key, Authorization: Bearer <JWT>
Store both tokens,
user_id, and your external_reference, all mapped to your customer. oauth.sign_in.completed and oauth.token.revoked identify the customer by externalReferenceId, which holds the external_reference you sent. Identity events use coinbaseUserId.
Refresh tokens
An expired access token returns401 Unauthorized. Exchange the refresh token for new tokens:
POST https://login.coinbase.com/oauth2/token
Authentication: OAuth client credentials, client_id and client_secret in the form body
refresh_token, so store the new one before you use the new access token. Sending an invalidated refresh token ends the link. Run one refresh at a time per customer. invalid_grant means the link was revoked or expired. The customer must link again.
Unlink an account
To disconnect the account, for example from a Disconnect Coinbase button, revoke either token:POST https://login.coinbase.com/oauth2/revoke
Authentication: OAuth client credentials, client_id and client_secret in the form body
200 response. Later calls return 401 until the customer links again.
2. Check the customer’s USDC balance
Check that the customer can cover the payment before you create it. This call needs thecoinbase:stablecoins:balance-read scope.
GET https://api.coinbase.com/v2/accounts/USDC
Authentication: Customer’s OAuth token, Authorization: Bearer <access_token>
available_to_send, not balance. balance includes funds on hold.
Amounts are decimal strings with six decimal places. Compare them as decimals, not as strings or floats.
The balance check is production only. In Sandbox, skip it and rely on the asynchronous
coinbase_insufficient_balance error.3. Create a payment session
POST https://api.cdp.coinbase.com/platform/v2/payment-sessions
Authentication: CDP API key, Authorization: Bearer <JWT>
4. Authorize the payment
Start a challenge, complete 2FA if Coinbase requires it, then submit the authorization. Every call uses the customer’s access token.Start an authorization challenge
Call this before every authorization. Coinbase decides whether 2FA is required.POST https://login.coinbase.com/api/v1/authorization-challenges
Authentication: Customer’s OAuth token, Authorization: Bearer <access_token>
Complete 2FA if required
- Redirect the customer to
challengeUrlto complete 2FA on a Coinbase page. - Poll
GET https://login.coinbase.com/api/v1/authorization-challenges?authorizationId={authorizationId}with the customer’s access token untilauthorizationStatusisAUTHORIZATION_STATUS_AUTHORIZED, or wait for theoauth.mfa.completedwebhook.
expiryTtl seconds.
Submit the authorization
This places a hold on the customer’s USDC balance.POST https://api.cdp.coinbase.com/platform/v2/payment-sessions/{paymentSessionId}/authorizations/coinbase
Authentication: Customer’s OAuth token, Authorization: Bearer <access_token>, not your CDP API key
{} if you have no metadata, externalReferenceId, or customerDisplay.referenceCode.
pending and moves to succeeded or failed. Track it with the acceptance.payment_session.authorization_succeeded and acceptance.payment_session.authorization_failed webhooks, or poll GET /v2/payment-sessions/{id}/authorizations/{authorizationId}.
The authorizationId in this response (authorization_…) identifies the payment authorization. It’s different from the challenge authorizationId you sent in cb-authz-id. Use this one to poll.
This endpoint doesn’t check the balance. A customer without enough USDC still gets
200 with status: "pending", then the authorization fails with coinbase_insufficient_balance. Run the balance check first.5. Capture, void, or refund
These calls use your CDP API key, as for any session. WithautoCapture: true, funds are captured after authorization succeeds. Otherwise, capture after fulfillment or void to release the hold. Refunds return funds to the customer’s Coinbase account. See Payment Sessions.
Charge without the customer present
For recurring or usage-based charges while the customer is away, start the challenge withinitiator: "merchant" and enforce2fa: false:
For the full recurring setup, including token refresh in billing jobs, see Recurring Payments.
Errors
Account linking
Errors return anerror field. The message is in error_message (init-oauth-session, confirm-link) or error_description (token, revoke).
Authorization challenge
Coinbase authorization
Errors specific toPOST .../authorizations/coinbase. See the API Reference for the full list.
Asynchronous authorization failures
Theacceptance.payment_session.authorization_failed webhook gives the reason in data.authorization.error.code:
These failures are final. To retry, create a new payment session. Match on
code, not message. For timed_out, internal_error, and invalid_parameters, contact support with the correlationId and session ID.
Webhooks
Besides payment session events, subscribe to OAuth and identity events to track each linked account:oauth.token.revokedfires when the customer disconnects your app in Coinbase, not when you call/oauth2/revoke. Don’t rely on it alone: also treatinvalid_granton refresh as a revoked link. A401on authorization can mean the access token expired, so refresh once and retry before you treat the link as revoked.- On
identity.user.deleted, stop processing payments for that customer.
What to read next
Payment Sessions
Expiries, balances, auto-capture, and target types
Pay with a Wallet
Let customers pay from a self-custody wallet
Recurring Payments
Charge a linked account on your schedule
Webhooks
Subscribe to payment and OAuth events
API Reference - Coinbase Auth
Coinbase authorization API