Skip to main content
This guide walks through a complete App2App setup. For a conceptual overview of how App2App works and why it uses device attestation, see the overview.
iOS only. App2App supports iOS with Apple App Attest today. Android isn’t supported yet, so use Coinbase-hosted Onramp for Android.

Prerequisites

  • Get allowlisted. Contact the Coinbase team to enable your CDP project for App2App and register your iOS App Attest identifier (teamID.bundleID).
  • Add your redirect host to the domain allowlist. In CDP Portal, under Payments → Onramp & Offramp, add the host of the redirectUrl you’ll use. See Security Requirements for supported formats. Requests with a redirectUrl host that isn’t allowlisted are rejected.
  • Use a physical iOS device. App Attest requires the Secure Enclave, which the iOS Simulator doesn’t have. This applies to sandbox testing too, not just production.
  • Set up Apple’s DeviceCheck framework. App2App calls DCAppAttestService directly: you’re working with Apple’s APIs, not a Coinbase SDK. You’ll need the App Attest capability enabled in Xcode (requires an Apple Developer Program account) and a deployment target of iOS 14+. See Apple’s DeviceCheck documentation, the App Attest setup guide, and the DCAppAttestService reference.
  • Add the Coinbase capability scheme so your app can detect whether the installed Coinbase app supports App2App:
This isn’t a Coinbase-side setting: iOS requires your app to declare any scheme it intends to query with canOpenURL, or the call always returns false. Only Coinbase app versions that support App2App register this particular scheme, which is what makes the check in Step 5 a valid capability check rather than a plain “is Coinbase installed” check. None of the attestation, challenge, or session requests below need a CDP API key or Authorization header; your app’s attestation is the credential. Confirming settlement is the exception: webhooks require a webhook subscription on your CDP project, and querying the transactions API directly requires a CDP API key JWT.
What is App Attest? App Attest is Apple’s on-device mechanism for proving that a request comes from your genuine, unmodified app running on real hardware. It uses a hardware-backed key stored in the device’s Secure Enclave. You call Apple’s DeviceCheck framework directly; Coinbase verifies the result server-side.

Setup steps

Step 1: Register your app’s attestation key

Do this once per install. Persist the registration locally, scoped to your CDP project ID. You don’t need to repeat it for every purchase. If a later purchase assertion fails because the key is invalid, clear your local registration, register a new key, and retry once.
1

Create a registration challenge

Send an empty request body. The response is { challenge, expiresAt }. challenge is base64url-encoded (unpadded).
2

Attest your key with Apple

Call DCAppAttestService.attestKey with:
Decode the challenge before hashing it. Hashing the base64url string directly fails verification. Apple returns { keyId, attestation }.
3

Register the public key

The response includes appId (the matched teamID.bundleID identifier), keyId, platform: "ios", and attestedAt.

Step 2: Create a purchase challenge

partnerUserRef is your user identifier. You generate it; Coinbase never creates or looks it up on its own. Send the same value every time for a given user (max 50 characters), and Coinbase echoes it back to you in three places so you can tie everything together:
The response is { challenge, expiresAt }. The challenge expires after 5 minutes. This endpoint doesn’t return a quote or eligibility details. To surface accurate information to the user before handoff, pull from:
  • Buy Config: supported countries and the payment methods available in each (plus US state-level subdivisions).
  • Buy Options: supported assets, networks, and fiat currencies, including the min/max limit for each payment method.
  • Buy Quote: an actual fee breakdown and total for a specific amount, asset, and payment method.
All three return estimates; Coinbase applies the signed-in user’s actual account-specific limits and eligibility after they land in the Coinbase app.
Want a quick visual reference instead of calling the APIs directly? The Onramp Asset Availability tool lets you look up supported assets, networks, and payment methods by region.

Step 3: Generate a purchase assertion

Call DCAppAttestService.generateAssertion with the key you registered in Step 1 and:
Use the challenge from Step 2, and apply the same decode-then-hash order as registration. Apple returns { keyId, assertion }.

Step 4: Create the session

The response is:
  • onrampUrl contains a session token that controls the destination, asset, network, and redirect for this purchase. Don’t append or modify query parameters on it.
  • This call is idempotent on the challenge: if you don’t receive a response (for example, due to a dropped connection), retry it. Retrying an already-attested challenge returns the same URL rather than an error.
  • The session token expires 15 minutes after it’s issued.

Step 5: Detect the Coinbase app and open the purchase

Before showing or enabling your “Pay with Coinbase” button, check whether the installed Coinbase app supports App2App:
  • If this returns true, open the onrampUrl from Step 4.
  • If it returns false, either the Coinbase app isn’t installed or the installed version doesn’t support App2App yet, so fall back to Coinbase-hosted Onramp instead.
  • Open onrampUrl exactly as returned. Don’t open the com.coinbase.cdp.onramp:// scheme yourself; it’s only used for the capability check above.
The session is single-use. Once the Coinbase app opens the URL, the session is consumed. If the user closes or force-quits the Coinbase app before finishing, create a new challenge and session rather than reopening the same URL.

Step 6: Handle the return to your app

The Coinbase app redirects back to your redirectUrl as a universal link with one of three outcomes. There’s no reason code on any of them; the query parameters below are everything you get:
  • handshakeNonce is reserved for a future verification step. You don’t need to do anything with it today; it’s safe to ignore. Both handshakeNonce and transactionId are only included on success today; Coinbase plans to also include them on error/cancelled when available.
  • error and cancelled carry no detail about what went wrong or how far the user got. If you need to distinguish “not eligible” from “transaction failed,” you can’t do it from the redirect alone.
  • If the user isn’t already signed in to Coinbase, they’ll sign in or create an account (and complete KYC if they’re new) before the purchase continues, all inside the Coinbase app, with no separate step needed from you.
Never credit a balance from the redirect. The redirect is for showing the right screen to your user, not for confirming settlement; it isn’t signed and shouldn’t be trusted on its own. Always confirm the purchase with a webhook or the transactions API before crediting anything.
Backgrounding the Coinbase app doesn’t lose the redirect: it fires normally once the user returns and completes or backs out of the flow, no matter how long they were away. The redirect is only permanently lost if the Coinbase app is force-quit or killed by the OS before the user comes back. Don’t block your UI waiting for a redirect indefinitely.

Step 7: Confirm the transaction

Subscribe to a webhook. Don’t build your primary confirmation path around polling. Coinbase sends a webhook to your backend when a transaction is created, updated, or settles. Set up a subscription once and get pushed a notification the moment a purchase completes, instead of pulling for it.
  1. Follow the Onramp & Offramp Webhooks guide to create a webhook subscription for onramp.transaction.success (and .created / .updated / .failed if you want the intermediate states).
  2. Match incoming events to your user with partnerUserRef; it’s included in every payload.
  3. Treat a transaction as settled only when the event is onramp.transaction.success (or, if you’re inspecting the payload directly, status is ONRAMP_TRANSACTION_STATUS_SUCCESS).
  4. App2App transactions include isAppToApp: true, so you can distinguish them from other purchase flows if needed.
If you’d rather pull instead of (or in addition to) receiving webhooks (for reconciliation, backfilling, or debugging a specific user), poll the Get onramp transactions by ID endpoint with a CDP API key JWT, using the same partnerUserRef you sent in Step 2:
  • Match transactions using partnerUserRef plus txHash, purchase currency, and amount. The redirect’s transactionId doesn’t match the transactionId returned by this endpoint: they’re generated by different systems for the same purchase, so don’t use them to join records, even though the redirect’s value now looks like a real ID on a live purchase rather than a placeholder.
  • Sandbox (dry-run) sessions are the exception to both of the above; see Sandbox testing.

Sandbox testing

You can exercise the entire flow (registration, challenge, session, handoff, and redirect) without moving real funds, using an Xcode debug build or TestFlight’s sandbox App Attest environment. No production App Attest entitlement is required to test.
1

Use a physical device

The iOS Simulator can’t attest: there’s no Secure Enclave. You need a physical device for every step that involves App Attest, in sandbox and production alike.
2

Prefix partnerUserRef with sandbox-

Send a partnerUserRef like sandbox-user-1234 on the purchase challenge in Step 2. This is what puts the session into sandbox mode; there’s no separate sandbox flag, endpoint, or allowlist. Once your app is allowlisted for App2App at all, the sandbox- prefix is all you need.
3

Run the flow with a development or sandbox App Attest key

Xcode debug builds and TestFlight’s sandbox App Attest environment are both accepted directly: register and attest exactly as you would in production. The one restriction: a development or sandbox App Attest key can only create a session when partnerUserRef has the sandbox- prefix. Using one of these keys without the prefix is rejected.
4

Confirm the sandbox purchase completes

The user experience is identical to a real purchase: sign-in, confirmation, and redirect all run normally. No funds move. The redirect still includes a transactionId, generated for that sandbox purchase only, but unlike a live purchase, no webhook fires and no row appears in the transactions API for a sandbox session; the redirect is the only confirmation signal you’ll get in sandbox.
  • Keep the sandbox- prefix until you’re ready to move real funds; it’s independent of which App Attest environment (development, sandbox, or production) you’re using.
  • Buy Options, Buy Quote, and Buy Config responses are estimates in both sandbox and production. After sign-in, Coinbase applies the user’s actual limits and eligibility.
  • Apple Pay sandbox testing isn’t available yet, so sandbox purchases can’t currently exercise the Apple Pay payment sheet.

Errors

Error responses may change. Coinbase will communicate breaking changes before they take effect whenever possible.