Skip to main content
Strong Customer Authentication (SCA) uses passkeys to protect sensitive operations. End users must approve each operation before Circle processes it. Each approval is bound to a specific operation and cannot be reused.
SCA applies to end users onboarded under Circle’s EU-regulated entity. This is based on the end user’s legal entity, not your platform’s. Contact your support team if you are unsure.
For an overview of how SCA works, see Strong Customer Authentication.
The SCA ceremony runs in the browser. There is no iOS, Android, or React Native SDK for it yet—native applications must host the ceremony in a web view or the system browser.

Prerequisites

Before you begin, ensure that you’ve:
  • Registered your application origin with Circle. Reach out to your support team with the exact origin (scheme + host, for example https://app.example.com). Registration is required separately for sandbox and production.
  • Installed the DAA web SDK.
  • Obtained a clientEntityId for the end user you’re enrolling. See Onboard customers for how to create one.
  • Run a device check for the end user and stored the deviceId it returned. See Collect device risk signals. Every protected operation below sends that deviceId in riskSignals.
Reuse the deviceId returned by checkDevice(). Do not generate one. Circle resolves it against the completed device check, and an unrecognized value is accepted and then declined: the endpoint returns 201 and the transaction later settles as failed.
The Digital Asset Accounts API base URL is https://api-sandbox.circle.com for sandbox and https://api.circle.com for production. Set your API key in the Authorization header using the format Bearer YOUR_API_KEY. See Sandbox environment and Going to production for environment details.

Steps

Step 1. Enroll a passkey

Enroll a passkey for each end user before they run any protected operation. Run this flow once per end user.
If your application origin is not registered, the enrollment ceremony appears to succeed—the passkey is created and the SDK returns normally—but the subsequent registration call silently times out and returns a generic Expired error after 5 minutes. Register your origin before running this flow.

Step 1.1. Create a registration session

Response
The frame token expires after 10 minutes. Pass it to your frontend right away. Do not re-send the request with the same idempotencyKey after delivering the token—this rotates the token and invalidates the copy you already sent. Start a fresh session with a new idempotencyKey if the token expires.

Step 1.2. Run the enrollment ceremony

Initialize the SDK on your frontend and call sca.enroll with the frame token from step 1.1.
The SDK renders the passkey prompt in a Circle-hosted iframe. Pass attestationResponse to your backend as-is. Re-serializing it causes a verification error.

Step 1.3. complete registration

Send the attestationResponse from the SDK to your backend. Then forward it to Circle to complete enrollment.
Response

Step 2. Approve a protected operation

For every protected operation, obtain a signed challenge and include it in the API request.

Step 2.1. Create a challenge

Response
The summary is a server-generated record of the operation. Do not use it to render a pre-confirmation to the end user—the Circle iframe shows its own description to the user.
The frame token expires after 5 minutes. Pass it to your frontend right away. Re-sending with the same idempotencyKey rotates the token and invalidates the copy you already sent. Create a new challenge with a fresh idempotencyKey if the token expires.
The intent must be identical to the operation request body—same fields, same values. An extra or missing field causes an intent mismatch error (420047). For the full list of intent shapes by operation, see the Create a challenge API reference.
The idempotencyKey in the intent must match the idempotencyKey in the operation request body. A mismatch causes an intent mismatch error.

Step 2.2. Run the approval ceremony

Pass the frameToken from the challenge response to the SDK’s approve method. The SDK renders the passkey prompt and returns a signed assertion string.

Step 2.3. Submit the protected operation

Include the challengeId and assertion as request headers. Use the same field values you set in the challenge intent.
Response
201 means the operation was accepted, not that it succeeded. A transfer is screened after it is created and can settle as failed. Poll GET /v1/accounts/transfers/{id} until status is terminal, and read errorCode and riskEvaluation on that response to see why—for example errorCode: transfer_denied with riskEvaluation.decision: denied. The list endpoint omits riskEvaluation, so fetch the transfer by id. A declined device check or an unrecognized deviceId surfaces here, not on the create call.
If the request was rejected before the transfer was created, the endpoint returns an error instead of 201.
If a protected endpoint returns HTTP 428 (error code 420058), the X-Sca-Challenge-Id or X-Sca-Assertion header was omitted from the request. Run the approval ceremony and retry with both headers present. For other SCA errors, see the Digital Asset Accounts API reference.