Skip to main content

Overview

By default, one MFA verification begins a session that authorizes every protected operation until the session expires. Request-scoped verification replaces the window with a challenge binding. Each approval authorizes exactly one request. CDP’s API associates a specific request with a challenge, the user approves that specific request, and resubmission of that initial request consumes the approval.
Request-scoped verification is opt-in per project. Projects that do not opt in keep session-based verification with no change.

Project configuration

Enable the Request verification scope in the CDP Portal. Resetting the scope back to Session restores the default behavior.
Switching from Session to Request revokes every outstanding verified session immediately. Users with an active session must verify again on their next protected operation. Switching from Request to Session revokes nothing.

How it works

Under Request scope, a protected operation that lacks an approval is rejected with 403 mfa_required. The rejection carries an X-Mfa-Challenge-Id response header that identifies a pending challenge for that exact request.
1

Register an MFA listener

Register a listener before any protected call. Under Request scope the challenge only exists once the server has rejected a call, so the SDK cannot prompt ahead of time. A protected call with no listener registered fails with the LISTENER_REQUIRED MFA error instead.
2

Call the protected operation

Call a protected operation such as sendEvmTransaction. The request shape is unchanged.
3

Receive the challenge

The server rejects the call with 403 mfa_required and an X-Mfa-Challenge-Id header. The SDK holds that challenge and invokes your listener with it as challengeId on the listener context.
4

Verify

For TOTP or SMS, call initiateMfaVerification and then submitMfaVerification with the user’s code. Both accept the challengeId from the listener context, and both fall back to the held challenge when you omit it.Passkey works differently. verifyPasskey runs the full WebAuthn ceremony in one call, and the assertion is cryptographically bound to the approved request. Do not pass a passkey assertion to submitMfaVerification.
5

Let the SDK replay the request

A successful verification releases the original call, which the SDK replays unchanged. The CDP API consumes the approval and the call proceeds. Do not retry it yourself.

TOTP and SMS

useRegisterMfaListener receives the challenge, and the protected action’s own promise resolves once verification completes. There is no mfa_required error to catch and no request to resubmit.
For SMS, pass mfaMethod: "sms" to both calls. The initiate call sends the code.A wrong code is terminal under Request scope. It consumes the challenge server-side, rejects submitMfaVerification, and rejects the sendEvmTransaction promise that was waiting on it. Handle both rejections, clear your local challengeId, and send the user back to a fresh protected call. Resubmitting against the spent challenge fails with mfa_flow_expired.

Passkey

Passkey verification needs a user gesture, so the listener records the challenge and renders a button rather than starting the ceremony itself.
Await verifyPasskeyAsync, not verifyPasskey. The latter resolves even on a cancelled prompt, which would clear your verification UI while the original call is still waiting.
CDPReactProvider registers a global MFA listener and renders the VerifyMfa modal for you, so the whole flow above runs with no wiring. Reach for a listener of your own when you want custom verification UI, and set mfa.disableAutoPrompt on the provider config to suppress the built-in modal.

Security properties

One approval, one request

An approval is bound to the digest of the approved request and is consumed on use. There is no window and no reuse:
  • Approving a signEvmTransaction call does not approve a sendEvmTransaction call, even with an identical body.
  • Approving a transfer of 25 USDC does not approve a transfer of 25,000 USDC.
  • Retrying after a consumed approval starts a new challenge and requires a new verification.

A failed code burns the challenge

Each challenge accepts one verification attempt. A wrong TOTP or SMS code deletes the challenge, so the next attempt must start over from the 403. Slow online guessing is further limited by the challenge lifetime.

Passkey binds cryptographically; TOTP and SMS bind server-side

The binding strength depends on the method:
  • Passkey: the WebAuthn challenge is derived from the approved request digest and the challenge ID. That value travels in clientDataJSON, which the credential signature covers, so the assertion is cryptographically bound to one request. A verifier holding the request and the challenge ID can recompute the value and check the signature against it, without trusting CDP’s record of the association.
  • TOTP and SMS: the code proves possession of the factor, and the server ties the verification to the request digest. The binding exists only in server state, so verifying it means trusting that state.
The authenticator signs an opaque digest. It does not parse or display the request, so the description the user approves comes from your UI rather than from the device. Show the user what they are about to authorize before you start the ceremony. TOTP and SMS are weaker than passkey under Request scope. Require passkey when you need evidence, checkable after the fact, that a user authorized one specific request.

Sessions are revoked when you harden

Setting verificationScope from session to request deletes every outstanding verified session for the project at the moment of the change. A session verified before the change cannot be spent after it.

Troubleshooting

The challenge is unknown, expired, or already spent. Challenges expire after 5 minutes and are deleted after a failed code. Restart the flow from the original protected call to receive a fresh X-Mfa-Challenge-Id.
The retried request does not match the approved request. The digest covers the method, path, query string, and body, so any change, including reordered or reformatted fields that change the semantic body, misses the approval. Retry with the identical request.
Under Request scope, initiate and submit must carry the challenge from the 403 response. Confirm the SDK version supports request-scoped verification and that your listener passes its challengeId through, or leaves it out so the SDK supplies the held challenge.

Overview

How MFA works and how sessions behave by default

MFA Prompts

Handle verification prompts with components, hooks, or the Core SDK

Passkeys

Enable passkey MFA for cryptographic per-request binding

Customizing Triggers

Which operations require MFA and how to customize prompts