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

# client.setupChallenge()

> Render the Challenge frame to resolve verification required by another flow.

Render the Challenge frame to resolve verification steps required by another
flow (for example, a buy transaction or an identity capture). The provider
presents the frame in a full-screen native modal. 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 or, for Customer API integrations, returned
in `kyc.challenge` by `PATCH /platform/v1/customers/{id}/kyc`. Pass it through
as-is and do not modify the URL yourself. If the URL doesn't carry a
`channelId` query parameter, the SDK generates one automatically.

For more context, see the [Handle
challenges](/platform/guides/handling-challenges) guide.

```tsx Setup challenge focus={6-50} theme={null}
import {
  useMoonPay,
  type BuyEvent,
  type ChallengeEvent,
} from "@moonpay/platform-sdk-react-native";

export function CheckoutScreen({ quoteSignature }: { quoteSignature: string }) {
  const { client } = useMoonPay();

  const start = async () => {
    const buyResult = await client.setupBuy({
      quote: quoteSignature,
      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,
          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

| Property  | Type                              | Required | Description                                                                                                                                                                                        |
| --------- | --------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`     | `string`                          | ✅        | The URL from the upstream flow's `challenge` event payload, or from an identity verification response. Pass it through unchanged. If the URL has no `channelId` query parameter, the SDK adds one. |
| `onEvent` | `(event: ChallengeEvent) => void` |          | Callback invoked for Challenge flow events. See [`ChallengeEvent`](#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.

| kind          | Payload                                               | When you receive it                                                                        |
| ------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `"ready"`     | —                                                     | The Challenge UI is rendered and visible to the customer.                                  |
| `"complete"`  | [`ChallengeCompleteResult`](#challengecompleteresult) | All verification steps resolved. Discriminated by `flow`.                                  |
| `"cancelled"` | [`ChallengeCancellation`](#challengecancellation)     | The customer dismissed the challenge. Discriminated by `flow`. Offer a retry path or exit. |
| `"error"`     | [`ChallengeEventError`](#challengeeventerror)         | The 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`:

| Field         | Type                          | Required | Description                                                                 |
| ------------- | ----------------------------- | -------- | --------------------------------------------------------------------------- |
| `flow`        | `"buy"` \| `"identity"`       | ✅        | Identifies which upstream flow the challenge resolved.                      |
| `transaction` | [`Transaction`](#transaction) |          | Present when `flow` is `"buy"`. The created or updated transaction.         |
| `identityId`  | `string`                      |          | Present when `flow` is `"identity"`. The identity record that was verified. |

#### `ChallengeCancellation`

The `cancelled` payload is discriminated by `flow`:

| Field            | Type                    | Required | Description                                                                              |
| ---------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `flow`           | `"buy"` \| `"identity"` | ✅        | Identifies which upstream flow was cancelled.                                            |
| `transactionId`  | `string`                |          | Present when `flow` is `"buy"`. The transaction associated with the cancelled challenge. |
| `challengeToken` | `string`                |          | Present 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`](/platform/sdk-reference/react-native/setup-buy#frametransaction) shape as `setupBuy()`.

| Field           | Type     | Required | Description                                                                |
| --------------- | -------- | -------- | -------------------------------------------------------------------------- |
| `status`        | `string` | ✅        | The transaction status. On the failure variant, `"failed"`.                |
| `id`            | `string` |          | Required on the non-failure variant; optional when `status` is `"failed"`. |
| `failureReason` | `string` |          | Present only on the failure variant (`status === "failed"`).               |

#### `ChallengeEventError`

| Field     | Type     | Required | Description                                                                                     |
| --------- | -------- | -------- | ----------------------------------------------------------------------------------------------- |
| `code`    | `string` | ✅        | A machine-readable error category propagated from the challenge frame. Surface to logs, not UI. |
| `message` | `string` | ✅        | Developer-friendly details.                                                                     |

## Result

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

### Result envelope

`Result<ChallengeFrame, SetupChallengeError>`

| Field   | Type                                          | Required | Description                      |
| ------- | --------------------------------------------- | -------- | -------------------------------- |
| `ok`    | `boolean`                                     | ✅        | Whether the operation succeeded. |
| `value` | [`ChallengeFrame`](#challengeframe)           |          | Present when `ok` is `true`.     |
| `error` | [`SetupChallengeError`](#setupchallengeerror) |          | Present when `ok` is `false`.    |

### `ChallengeFrame`

| Field     | Type         | Required | Description                                                                                           |
| --------- | ------------ | -------- | ----------------------------------------------------------------------------------------------------- |
| `dispose` | `() => void` | ✅        | Unmounts 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`

| Field     | Type                                       | Required | Description                 |
| --------- | ------------------------------------------ | -------- | --------------------------- |
| `kind`    | `"configurationError"` \| `"genericError"` | ✅        | The error category.         |
| `message` | `string`                                   | ✅        | Developer-friendly details. |

<Accordion title="TS Definitions">
  ```ts types.ts theme={null}
  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;
  };
  ```
</Accordion>
