Prerequisites
- A customer in a payment or onboarding flow: connected through the connect flow, logged in through the Auth frame on the Customer API path, or on the guest checkout path.
- A UI surface where you can render frames (WebView on mobile, iframe on web).
When challenges appear
Challenges are extra steps a customer must complete before MoonPay can continue an action. Common reasons include:- Strong Customer Authentication (SCA), for example 3D Secure
- Identity verification (KYC)
- Card-specific checks such as CVC re-entry or micro-authorization
challenge event that points to the
Challenge frame. The Customer API returns a
kyc.challenge URL when verification needs a hosted step. A buy quote returns
a challenge object when the customer must clear a step before the quote
becomes executable.
Where challenges are emitted
Each emitting surface hands you a fully-formed URL that you load into the dedicated Challenge frame:- The Buy Button frame emits
challengefor Apple Pay, Google Pay, card, and bank transfer transactions, including guest checkout and headless mode. See Pay with card and Pay with bank transfer. - The Customer API returns
kyc.challenge({ url, expiresAt }) onPATCH /customers/{id}/kycwhen verification needs a hosted step, and on subsequentGET /customers/{id}responses while the challenge is outstanding. Render theurlin the Challenge frame the same way you render a frame-emitted challenge URL. See Handle the hosted challenge. - The buy quote returns
challenge({ kind, url }) alongsideexecutable: falsewhen a guest checkout customer can raise their spending limit. This is the only surface that raises a challenge before a payment frame exists, so you resolve it and request the quote again rather than resuming a transaction. See Upgrade a guest account.
On guest checkout, second-factor authentication and KYC step-up both arrive as
the same
challenge event for Apple Pay and Google Pay. Render the Challenge
frame the same way.challenge event uses the same envelope across all of the frames. The
quote returns the same kind and url fields, without the event wrapper:
Example challenge event
How to handle a challenge
- Listen for the
challengeevent on the frame. Treat the action as blocked until the challenge resolves. - Mount the Challenge frame at the URL from the event payload. Pass the URL through as-is — do not construct or modify it. See the Challenge frame reference for the frame URL, parameters, and events.
- Handle the Challenge frame events:
ready— the challenge UI is rendered and visible.complete— verification resolved. The Challenge frame reports any downstream artifacts (for example, the transactionidandstatusfor the buy flow). For the identity flow, checkstatusfirst:pendingorunavailablemeans the customer isn’t verified. See Pending customers and Unavailable customers.resolved— sent instead ofcompleteonly when you opt in withresume. Verification is done and the customer pays on your buy button. The Challenge frame then sends nocompletefor this purchase. The transaction arrives on your buy button’scompleteafter the customer taps it again. See Finish on your buy button.cancelled— the customer dismissed the challenge. Offer a retry path.error— the challenge failed. Log thecodeandmessage(both are developer-facing and not intended for end-user UI) and show the customer a generic next step, such as retrying or choosing a different payment method. Forcustomer_status_pendingorcustomer_status_unavailable, don’t treat it as a failure: follow Pending customers or Unavailable customers.
- Tear down the originating frame after
complete,cancelled, orerror. For the buy button, callbuyResult.value.dispose(). A quote-emitted challenge has no originating frame, so dispose the Challenge frame itself. Afterresolved, keep the buy button mounted: it sends the purchase’scomplete.
Finish on your buy button
By default, once a buy challenge is done, the Challenge frame shows its own Apple Pay or Google Pay confirm screen and emitscomplete. To keep the
customer on the buy button you
rendered, pass resume: true to setupChallenge(). It needs
@moonpay/platform-sdk-web or @moonpay/platform-sdk-react-native 1.27.0 or
later. This applies to the challenges the buy button raises for a connected
customer’s Apple Pay or Google Pay purchase before it creates the transaction,
such as SCA or a wallet-ownership check, and to guest checkout recovery.
With resume, the Challenge frame emits resolved instead of complete once
the challenges are resolved. Dispose the Challenge frame, not the buy button,
and show the buy button again. The customer taps it to pay, and the buy button
emits complete with the transaction. After resolved, the Challenge frame
sends no complete for this purchase. If the Challenge
frame can’t hand the purchase back, it shows its own confirm screen and emits
complete as it does without resume, so keep your complete handler.
resume only changes Apple Pay and Google Pay purchases. For a card payment,
the Challenge frame completes the purchase itself and emits complete, with or
without resume. The same holds for a challenge raised after the transaction
is created, such as a liveness check. Render the Challenge frame where it
shares browser storage with the buy button, such as in the same page. See
Finish on your buy
button.
resume also covers guest checkout recovery: the challenge the buy button
raises when a guest purchase needs the customer to sign in, for example for a
second factor. Once the customer has signed in and any further verification is
done, the Challenge frame emits resolved. Show the buy button again, and the
purchase completes as the signed-in customer when the customer taps it.
In that case sessionUpgraded: true arrives on the purchase’s final complete:
the buy button’s, or the Challenge frame’s if the frame finishes the purchase
itself. Keep both checks. The SDK types include sessionUpgraded on the buy
button’s complete from 1.28.0. Create a new session and check the connection
again. See Recover the connection after
a step-up.
Do this on complete. sessionUpgraded is never on resolved, cancelled, or
error, and a new session on resolved remounts the buy button in the middle
of the purchase.
For the message flow, every event in order, and the code, see Finish on your
buy button.
Implementation tips
- Use a full-screen surface on mobile: challenge flows often involve authentication or verification, so treat them like a separate screen or full sheet.
- Validate
postMessageevents: if you integrate frames manually, validate origin and message shape. The frames protocol documents the shared envelope format. - Handle cancellation and timeouts: if the customer closes the challenge or it fails, show a clear next step (retry, choose a different payment method, or exit the flow).