> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding Captcha Protection

> [card] Recommended: JavaScript SDK for React Native
>
> While this SDK is still supported, we recommend using newer [JavaScript SDK](https://docs.dynamic.xyz/docs/javascript/reference/react-native-quickstart.md), which is optimized for React Native, but also comes with a host of other benefits.

## Summary

Dynamic supports two captcha providers to protect your app from bots:

* **Cloudflare Turnstile**: Dynamic manages the keys by default, or bring your own site key and secret key.
* **hCaptcha**: Requires your own credentials.

Configure your provider in the Dynamic Console under **Fraud Protections → Captcha**, then follow the implementation steps below.

## Setting up Cloudflare Turnstile

> [!NOTE]
>
> Cloudflare Turnstile requires Dynamic SDK version **4.92.4** or later.

The managed widget is enabled by default with no Cloudflare account required.

1. In the Dynamic Console, go to **Fraud Protections → Captcha**.
2. Select **Cloudflare Turnstile** as your provider.
3. Optionally supply your own site key and secret key for widget customization.
4. Toggle captcha **on**.

## Setting up hCaptcha

hCaptcha requires your own account.

1. Sign up at [hcaptcha.com](https://www.hcaptcha.com/) (Pro plan recommended for passive/friction-free modes).
2. Add a new site with the hostname where your app operates.
3. Copy your **site key** (used client-side) and **secret key** (used server-side).
4. In the Dynamic Console, go to **Fraud Protections → Captcha**, select **hCaptcha**, enter both keys, and enable the toggle.

## Implementation

### Prerequisites

Install the captcha package and `react-native-webview`:

```sh theme={"system"}
pnpm add @dynamic-labs-sdk/react-native-captcha
npx expo install react-native-webview
```

### 1. Render the widget and set the token

Use `getCaptchaSettings` to determine which provider is active, then render the matching challenge component. Each component loads the provider's widget in a WebView and calls `onToken` with the captcha token when solved. Pass the token to `setCaptchaToken` before your sign-in request.

The `baseUrl` prop is the origin the widget document is served under. hCaptcha and Turnstile site keys only render on whitelisted hostnames, so set this to a domain you have added to your provider's site configuration.

> [tab] Cloudflare Turnstile
>
> ```tsx theme={"system"}
> import { useState } from 'react';
> import { View, Button } from 'react-native';
> import { CaptchaProviderEnum, getCaptchaSettings, setCaptchaToken } from '@dynamic-labs-sdk/client';
> import { TurnstileChallenge } from '@dynamic-labs-sdk/react-native-captcha';
>
> function SignInScreen() {
>   const [captchaReady, setCaptchaReady] = useState(false);
>   const settings = getCaptchaSettings();
>   const siteKey = settings?.siteKey;
>
>   const handleSignIn = async () => {
>     // call sendEmailOTP, auth.wallet.connect, etc.
>   };
>
>   if (settings?.provider !== CaptchaProviderEnum.CloudflareTurnstile || !siteKey) {
>     return <Button title="Sign in" onPress={handleSignIn} />;
>   }
>
>   const handleToken = (token: string) => {
>     setCaptchaToken({ captchaToken: token });
>     setCaptchaReady(true);
>   };
>
>   return (
>     <View>
>       <TurnstileChallenge
>         baseUrl="https://your-whitelisted-origin.com"
>         siteKey={siteKey}
>         onToken={handleToken}
>         onError={(error) => console.error(error.message)}
>       />
>       <Button title="Sign in" disabled={!captchaReady} onPress={handleSignIn} />
>     </View>
>   );
> }
> ```

> [tab] hCaptcha
>
> ```tsx theme={"system"}
> import { useState } from 'react';
> import { View, Button } from 'react-native';
> import { CaptchaProviderEnum, getCaptchaSettings, setCaptchaToken } from '@dynamic-labs-sdk/client';
> import { HCaptchaChallenge } from '@dynamic-labs-sdk/react-native-captcha';
>
> function SignInScreen() {
>   const [captchaReady, setCaptchaReady] = useState(false);
>   const settings = getCaptchaSettings();
>   const siteKey = settings?.siteKey;
>
>   const handleSignIn = async () => {
>     // call sendEmailOTP, auth.wallet.connect, etc.
>   };
>
>   if (settings?.provider !== CaptchaProviderEnum.Hcaptcha || !siteKey) {
>     return <Button title="Sign in" onPress={handleSignIn} />;
>   }
>
>   const handleToken = (token: string) => {
>     setCaptchaToken({ captchaToken: token });
>     setCaptchaReady(true);
>   };
>
>   return (
>     <View>
>       <HCaptchaChallenge
>         baseUrl="https://your-whitelisted-origin.com"
>         siteKey={siteKey}
>         onToken={handleToken}
>         onError={(error) => console.error(error.message)}
>       />
>       <Button title="Sign in" disabled={!captchaReady} onPress={handleSignIn} />
>     </View>
>   );
> }
> ```

When the user completes the captcha challenge, the token is set and the sign-in button becomes enabled. If the widget errors, the `onError` callback fires with a typed error so you can prompt the user to retry.

Both `HCaptchaChallenge` and `TurnstileChallenge` accept an optional `style` prop to override the default container styling (for example, to change the height or border radius), and an optional `logLevel` prop to control the component's internal logging (defaults to `'warn'`).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.