> ## 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.

# Challenge

> Details on working with the Challenge frame used by the [Pay with card](/platform/guides/pay-with-card) flow and by identity verification.

The Challenge frame handles verification steps required by another flow. Two
upstream flows mount it today:

* The [buy frame](/platform/frames/buy) emits a `challenge` event with a URL —
  use that URL to mount the frame and complete the card transaction.
* The identity flow mounts the frame directly to capture identity data (for
  example, document capture or liveness checks).

The frame is self-driving: after the handshake, there are no further
parent-to-child messages. It sequences through all required verification steps
and emits `complete` when the pipeline finishes. The `complete` and
`cancelled` payloads are discriminated by `flow` so you can branch on the
originating flow.

## URL

For the `buy` flow, the URL is provided by the buy frame's `challenge` event
payload. For the `identity` flow, the URL is provided by the upstream identity
verification call. In both cases, do not construct or modify the URL yourself.

## Requirements

### Size

Render the frame in a modal or full sheet so the customer can complete
verification. The frame adapts to any size, but a full-screen or large modal
works best for flows like 3D Secure that load bank-hosted pages.

## Initialization parameters

| Property         | Type     | Required | Description                                                                                                           |
| ---------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `clientToken`    | `string` | ✅        | Included automatically in the challenge URL for CSP compliance. Do not remove it.                                     |
| `channelId`      | `string` | ✅        | Generate a fresh channel ID on your side and append it to the challenge URL before setting it as the frame `src`.     |
| `challengeToken` | `string` | ✅        | Opaque token. Do not modify or parse it.                                                                              |
| `theme`          | `string` |          | Pass `dark` or `light` to force a specific appearance. If you omit this, the frame uses the user's system appearance. |

## Events

All events are dispatched using the message pattern described in the [frames
protocol](/platform/frames/overview#frames-protocol#messages). Below are the
event payloads specific to the Challenge frame.

### Outbound events

<Badge size="md" className="px-2" color="purple">
  frame->parent
</Badge>

These events are sent from this frame to the parent window.

#### `handshake`

The frame requests that you open a message channel.

<CodeGroup>
  ```json Example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "handshake"
  }
  ```

  ```ts twoslash TypeScript definition theme={null}
  type Message<T> = T & {
    version: 2;
    meta: { channelId: string };
  };

  type HandshakeEvent = Message<{
    kind: "handshake";
  }>;
  ```
</CodeGroup>

#### `ready`

The challenge UI is rendered and visible to the customer.

<CodeGroup>
  ```json Example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "ready"
  }
  ```

  ```ts twoslash TypeScript definition theme={null}
  type Message<T> = T & {
    version: 2;
    meta: { channelId: string };
  };

  type ReadyEvent = Message<{
    kind: "ready";
  }>;
  ```
</CodeGroup>

#### `complete`

All required verification steps have resolved. Remove the challenge frame and
branch on the discriminated payload:

* `flow: "buy"` — the transaction pipeline finished. Remove the buy frame too
  and navigate to the confirmation screen.
* `flow: "identity"` — the customer was verified. Use `identityId` to continue
  your onboarding flow.

<CodeGroup>
  ```json Buy example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "complete",
    "payload": {
      "flow": "buy",
      "transaction": {
        "id": "txn_01",
        "status": "pending"
      }
    }
  }
  ```

  ```json Identity example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "complete",
    "payload": {
      "flow": "identity",
      "identityId": "idn_01"
    }
  }
  ```

  ```ts twoslash TypeScript definition theme={null}
  type Message<T> = T & {
    version: 2;
    meta: { channelId: string };
  };

  type ChallengeCompleteEvent = Message<{
    kind: "complete";
    payload:
      | {
          flow: "buy";
          transaction: {
            id: string;
            status: "pending" | "completed" | "failed";
          };
        }
      | {
          flow: "identity";
          identityId: string;
        };
  }>;
  ```
</CodeGroup>

#### `cancelled`

The customer dismissed the challenge. Remove the challenge frame and act on
the discriminated payload:

* `flow: "buy"` — remove the buy frame too, then offer a retry path. The
  payload carries `transactionId` and `challengeToken` (when known) so you
  can resume without restarting the pipeline.
* `flow: "identity"` — offer a retry path or exit. No extra payload is
  emitted.

<CodeGroup>
  ```json Buy example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "cancelled",
    "payload": {
      "flow": "buy",
      "transactionId": "txn_01",
      "challengeToken": "eyJhbGciOi..."
    }
  }
  ```

  ```json Identity example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "cancelled",
    "payload": {
      "flow": "identity"
    }
  }
  ```

  ```ts twoslash TypeScript definition theme={null}
  type Message<T> = T & {
    version: 2;
    meta: { channelId: string };
  };

  type ChallengeCancelledEvent = Message<{
    kind: "cancelled";
    payload:
      | {
          flow: "buy";
          transactionId?: string;
          challengeToken?: string;
        }
      | {
          flow: "identity";
        };
  }>;
  ```
</CodeGroup>

#### `error`

The challenge failed. Remove the challenge frame (and the buy frame, if the
buy flow was driving it), then surface the message to the developer.

<CodeGroup>
  ```json Example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "error",
    "payload": {
      "code": "invalid_challenge",
      "message": "Could not decode challenge from response."
    }
  }
  ```

  ```ts twoslash TypeScript definition theme={null}
  type Message<T> = T & {
    version: 2;
    meta: { channelId: string };
  };

  type ChallengeErrorEvent = Message<{
    kind: "error";
    payload: {
      /**
       * A machine-readable error category. Surface to logs, not UI. Additional
       * codes can be propagated from the upstream flow.
       */
      code: string;
      /** A developer-facing error message. Not intended to be rendered in UI. */
      message: string;
    };
  }>;
  ```
</CodeGroup>

Common codes:

| Code                 | Description                                                                |
| -------------------- | -------------------------------------------------------------------------- |
| `configurationError` | Frame initialization failed (for example, the `challengeToken` is missing) |
| `invalid_token`      | The `challengeToken` could not be decoded                                  |
| `unsupported_flow`   | The token type is not supported by this frame                              |
| `invalid_challenge`  | Challenge details could not be decoded from the token                      |
| `missing_quote`      | The buy flow could not decode the quote from the `challengeToken`          |

### Inbound events

<Badge size="md" className="px-2">
  parent->frame
</Badge>

These events are sent from the parent window to this frame.

#### `ack`

Acknowledge the [handshake](#handshake). This is the only message you send to
the challenge frame — it is self-driving after the handshake.

<CodeGroup>
  ```json Example theme={null}
  {
    "version": 2,
    "meta": { "channelId": "ch_challenge_1" },
    "kind": "ack"
  }
  ```

  ```ts twoslash TypeScript definition theme={null}
  type Message<T> = T & {
    version: 2;
    meta: { channelId: string };
  };

  type AckEvent = Message<{
    kind: "ack";
  }>;
  ```
</CodeGroup>
