Skip to main content

Documentation Index

Fetch the complete documentation index at: https://dev.moonpay.com/llms.txt

Use this file to discover all available pages before exploring further.

Render the Challenge frame into your UI to resolve verification steps required by another flow (for example, a buy transaction or an identity capture). The challenge frame is self-driving — after initialization, it sequences through all required verification steps and emits complete when the pipeline finishes. Unlike other setup methods, setupChallenge() takes a url provided by the upstream flow’s challenge event. Pass it through as-is — the URL already carries the channelId the SDK needs to wire up the frame. Do not modify the URL yourself. For more context, see the Handle challenges guide.
Setup challenge
import {
  createClient,
  type BuyEvent,
  type ChallengeEvent,
} from "@moonpay/platform-sdk-web";

const client = createClient({ sessionToken: "c3N0XzAwMQ==" });

const buyResult = await client.setupBuy({
  quote: quoteResult.value.data.signature,
  container: document.querySelector("#buyContainer"),
  onEvent: async (event: BuyEvent) => {
    if (event.kind !== "challenge") return;

    // The url comes from the challenge event — pass it through as-is.
    const challengeResult = await client.setupChallenge({
      url: event.payload.url,
      container: document.querySelector("#challengeContainer"),
      onEvent: (event: ChallengeEvent) => {
        switch (event.kind) {
          case "ready":
            // Challenge UI is rendered and visible to the customer
            break;
          case "complete":
            if (event.payload.flow === "buy") {
              console.log(event.payload.transaction);
            } else if (event.payload.flow === "identity") {
              console.log(event.payload.identityId);
            }
            buyResult.value.dispose();
            break;
          case "cancelled":
            // Customer dismissed the challenge — offer a retry path
            buyResult.value.dispose();
            break;
          case "error":
            console.error(event.payload.message);
            buyResult.value.dispose();
            break;
        }
      },
    });

    if (!challengeResult.ok) {
      // Handle error
      console.error(challengeResult.error.kind, challengeResult.error.message);
      return;
    }
  },
});

Parameters

PropertyTypeRequiredDescription
urlstringThe URL from the upstream flow’s challenge event payload. Pass it through unchanged. The URL must include the channelId query parameter (the upstream flow always sets it).
containerHTMLElementA DOM element to render the Challenge frame into.
onEvent(event: ChallengeEvent) => voidCallback invoked for Challenge flow events. See ChallengeEvent.
This method does not require a separate auth token. The client uses stored credentials from an active connection.

ChallengeEvent

onEvent receives events as the challenge flow progresses. Use event.kind to decide how to handle each event. The complete and cancelled payloads are discriminated by payload.flow so you can branch on the originating flow.
kindPayloadWhen you receive it
"ready"The Challenge UI is rendered and visible to the customer.
"complete"ChallengeCompleteResultAll verification steps resolved. Discriminated by flow.
"cancelled"ChallengeCancellationThe customer dismissed the challenge. Discriminated by flow. Offer a retry path or exit.
"error"ChallengeEventErrorThe challenge failed with a terminal error.
The challenge frame is self-driving. After acknowledging the initial handshake, the SDK does not send further messages to the frame. The frame internally handles all verification types automatically — including CVC confirmation, 3D Secure, identity verification (KYC), Strong Customer Authentication (SCA), micro-deposit authorization, and wallet ownership proof. You never need to distinguish between them.

ChallengeCompleteResult

The complete payload is discriminated by flow:
FieldTypeRequiredDescription
flow"buy" | "identity"Identifies which upstream flow the challenge resolved.
transactionTransactionPresent when flow is "buy". The created or updated transaction.
identityIdstringPresent when flow is "identity". The identity record that was verified.

ChallengeCancellation

The cancelled payload is discriminated by flow:
FieldTypeRequiredDescription
flow"buy" | "identity"Identifies which upstream flow was cancelled.
transactionIdstringPresent when flow is "buy". The transaction associated with the cancelled challenge.
challengeTokenstringPresent when flow is "buy". The challenge token from the original challenge event.

Transaction

This is the transaction object returned when the buy challenge completes. It uses the same FrameTransaction shape as setupBuy().
FieldTypeRequiredDescription
statusstringThe transaction status. On the failure variant, "failed".
idstringRequired on the non-failure variant; optional when status is "failed".
failureReasonstringPresent only on the failure variant (status === "failed").

ChallengeEventError

FieldTypeRequiredDescription
codestringA machine-readable error category propagated from the challenge frame. Surface to logs, not UI.
messagestringDeveloper-friendly details.

Result

client.setupChallenge() returns a Result<ChallengeFrame, SetupChallengeError>.

Result envelope

Result<ChallengeFrame, SetupChallengeError>
FieldTypeRequiredDescription
okbooleanWhether the operation succeeded.
valueChallengeFramePresent when ok is true.
errorSetupChallengeErrorPresent when ok is false.

ChallengeFrame

FieldTypeRequiredDescription
dispose() => voidUnmounts the frame. After you call this, no further events are dispatched to your onEvent callback.
Unlike setupBuy(), setupApplePay(), and setupGooglePay(), the ChallengeFrame does not expose a setQuote() method. The challenge frame runs to completion on its own.

SetupChallengeError

FieldTypeRequiredDescription
kind"configurationError" | "genericError"The error category.
messagestringDeveloper-friendly details.
types.ts
type ChallengeFrame = {
  dispose: () => void;
};

type FrameTransaction =
  | { id: string; status: string }
  | { id?: string; status: "failed"; failureReason: string };

type ChallengeCompleteResult =
  | { flow: "buy"; transaction: FrameTransaction }
  | { flow: "identity"; identityId: string };

type ChallengeCancellation =
  | { flow: "buy"; transactionId?: string; challengeToken?: string }
  | { flow: "identity" };

type ChallengeEvent =
  | { kind: "ready" }
  | { kind: "complete"; payload: ChallengeCompleteResult }
  | { kind: "cancelled"; payload: ChallengeCancellation }
  | { kind: "error"; payload: ChallengeEventError };

type ChallengeEventError = {
  code: string;
  message: string;
};

type SetupChallengeError = {
  kind: "configurationError" | "genericError";
  message: string;
};