> This page is for Applications Protection.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.humansecurity.com/llms.txt.

# React Native Integration (Legacy)

The SDK can be integrated into a [React Native](https://reactnative.dev/) project.

> **Warning**
>
> This integration is the legacy version. For the most recent version, see our [recommended integration](/applications/react-native-integration-recommended).

### Integration with C++ (iOS)

If your project is built with C++ (`AppDelegate.mm`), you should do the following:

1. Open Xcode Build Settings.
2. Navigate to **Targets** -> **Build Settings** -> `Apple Clang - Custom Compiler Flags` -> `Other C++ Flags`.
3. Add the `-fcxx-modules` flag.

## Add the Human Module

### Android

1. Create a [native module](https://reactnative.dev/docs/legacy/native-modules-android) called `HumanModule`.

**Java / HumanModule.java:**

```java
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.humansecurity.mobile_sdk.HumanSecurity;
import com.humansecurity.mobile_sdk.main.HSBotDefenderChallengeResult;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Objects;

public class HumanModule extends ReactContextBaseJavaModule {

    static HumanModule shared = null;

    String strNewHeaders = "HumanNewHeaders";
    String strChallengeResult = "HumanChallengeResult";
    String strSolved = "solved";
    String strCancelled = "cancelled";
    String strFalse = "false";

    HumanModule(ReactApplicationContext context) {
        super(context);
    }

    @NonNull
    @Override
    public String getName() {
        return "HumanModule";
    }

    public void handleUpdatedHeaders(HashMap<String, String> headers) {
        if (!this.getReactApplicationContext().hasCatalystInstance()) {
            return;
        }
        JSONObject json = new JSONObject(headers);
        this.getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(strNewHeaders, json.toString());
    }

    public void handleChallengeSolvedEvent() {
        if (!this.getReactApplicationContext().hasCatalystInstance()) {
            return;
        }
        this.getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(strChallengeResult, strSolved);
    }

    public void handleChallengeCancelledEvent() {
        if (!this.getReactApplicationContext().hasCatalystInstance()) {
            return;
        }
        this.getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(strChallengeResult, strCancelled);
    }

    @ReactMethod
    public void getHTTPHeaders(Promise promise) {
        JSONObject json = new JSONObject(Objects.requireNonNull(HumanSecurity.INSTANCE.getBD().headersForURLRequest(null)));
        promise.resolve(json.toString());
    }

    @ReactMethod
    public void handleResponse(String response, String url, Promise promise) {
        boolean handled = HumanSecurity.INSTANCE.getBD().handleBlockResponse(response, url, result -> {
            promise.resolve(result == HSBotDefenderChallengeResult.SOLVED ? strSolved : strCancelled);
            return null;
        });
        if (!handled) {
            promise.resolve(strFalse);
        }
    }
}
```

2. Add the `HumanModule` to the `HumanPackage` and set the `shared` property.

**Java / HumanPackage.java:**

```java
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HumanPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        HumanModule module = new HumanModule(reactContext);
        HumanModule.shared = module;
        modules.add(module);

        return modules;
    }
}
```

### iOS

Create a [native module](https://reactnative.dev/docs/legacy/native-modules-ios) called `HumanModule`.

**Objective-C / HumanModule.h:**

```objectivec
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

NS_ASSUME_NONNULL_BEGIN

@interface HumanModule : RCTEventEmitter <RCTBridgeModule>

+ (HumanModule *)shared;
- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers;
- (void)handleChallengeSolvedEvent;
- (void)handleChallengeCancelledEvent;

@end

NS_ASSUME_NONNULL_END
```

**Objective-C / HumanModule.m:**

```objectivec
#import "HumanModule.h"
@import HUMAN;

static NSString *strNewHeaders = @"HumanNewHeaders";
static NSString *strChallengeResult = @"HumanChallengeResult";
static NSString *strSolved = @"solved";
static NSString *strCancelled = @"cancelled";
static NSString *strFalse = @"false";

@implementation HumanModule

static HumanModule *shared = nil;

+ (HumanModule *)shared {
  return shared;
}

- (instancetype)init {
  self = [super init];
  shared = self;
  return self;
}

- (NSArray<NSString *> *)supportedEvents {
  return @[strNewHeaders, strChallengeResult];
}

- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers {
  NSData *data = [NSJSONSerialization dataWithJSONObject:headers options:0 error:nil];
  NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  [self sendEventWithName:strNewHeaders body:json];
}

- (void)handleChallengeSolvedEvent {
  [self sendEventWithName:strChallengeResult body:strSolved];
}

- (void)handleChallengeCancelledEvent {
  [self sendEventWithName:strChallengeResult body:strCancelled];
}

RCT_EXPORT_MODULE(HumanModule);

RCT_REMAP_METHOD(getHTTPHeaders,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  NSDictionary<NSString *, NSString *> *humanHttpHeaders = [HumanSecurity.BD headersForURLRequestForAppId:nil];
  NSData *data = [NSJSONSerialization dataWithJSONObject:humanHttpHeaders options:0 error:nil];
  NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  resolve(json);
}

RCT_REMAP_METHOD(handleResponse,
                 response:(NSString *)response
                 url:(NSString *)url
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
  BOOL handled = [HumanSecurity.BD handleBlockResponseWithData:data url:url callback:^(enum HSBotDefenderChallengeResult result) {
    resolve((result == HSBotDefenderChallengeResultSolved ? strSolved : strCancelled));
  }];
  if (!handled) {
    resolve(strFalse);
  }
}

@end
```

## How to start the SDK

* The most important thing is to start the SDK as soon as possible in your app flow. The reason for that is that when your app sends a URL request to your server before the SDK was started, the request will not include the SDK's HTTP headers. As a result, HUMAN's Enforcer could block the request, and the SDK will not be able to present a challenge to the user. The best place to start the SDK is in the:
  * `Application`'s `onCreate` function on Android.
  * `AppDelegate`'s `didFinishLaunchingWithOptions` function on iOS.
* You should start the SDK on the **main thread**.
* Implement the `HumanDelegate` to receive events from the SDK.

Here is an example of how it should be:

### Android

**Java / MainApplication.java:**

```java
import android.app.Application;
import android.content.Context;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.soloader.SoLoader;
import com.human_demo.newarchitecture.MainApplicationReactNativeHost;
import com.humansecurity.mobile_sdk.HumanSecurity;
import com.humansecurity.mobile_sdk.main.HSBotDefenderDelegate;
import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType;
import com.humansecurity.mobile_sdk.main.policy.HSPolicy;

public class MainApplication extends Application implements ReactApplication, HSBotDefenderDelegate {

    private final ReactNativeHost mReactNativeHost =
            new ReactNativeHost(this) {
                @Override
                public boolean getUseDeveloperSupport() {
                    return BuildConfig.DEBUG;
                }

                @Override
                protected List<ReactPackage> getPackages() {
                    List<ReactPackage> packages = new PackageList(this).getPackages();
                    packages.add(new HumanPackage());
                    return packages;
                }

                @Override
                protected String getJSMainModuleName() {
                    return "index";
                }
            };

    private final ReactNativeHost mNewArchitectureNativeHost =
            new MainApplicationReactNativeHost(this);

    @Override
    public ReactNativeHost getReactNativeHost() {
        if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
            return mNewArchitectureNativeHost;
        } else {
            return mReactNativeHost;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
        SoLoader.init(this, false);
        initializeFlipper(this, getReactNativeHost().getReactInstanceManager());

        startHumanSDK();
    }

    private static void initializeFlipper(
            Context context, ReactInstanceManager reactInstanceManager) {
        if (BuildConfig.DEBUG) {
            try {
                Class<?> aClass = Class.forName("<package_name>.ReactNativeFlipper");
                aClass
                        .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
                        .invoke(null, context, reactInstanceManager);
            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
                     InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    private void startHumanSDK() {
        HSPolicy policy = new HSPolicy();
        policy.getAutomaticInterceptorPolicy().setInterceptorType(HSAutomaticInterceptorType.NONE);
        try {
            HumanSecurity.INSTANCE.start(this, "<APP_ID>", policy);
            HumanSecurity.INSTANCE.getBD().setDelegate(this);
        }
        catch (Exception exception) {
            Log.e("MainApplication","Exception: " + exception.getMessage());
        }
    }

    @Override
    public void botDefenderDidUpdateHeaders(@NonNull HashMap<String, String> headers, @NonNull String appId) {
        if (HumanModule.shared != null) {
            HumanModule.shared.handleUpdatedHeaders(headers);
        }
    }

    @Override
    public void botDefenderRequestBlocked(@Nullable String url, @NonNull String appId) {

    }

    @Override
    public void botDefenderChallengeSolved(@NonNull String appId) {
        HumanModule.shared.handleChallengeSolvedEvent();
    }

    @Override
    public void botDefenderChallengeCancelled(@NonNull String appId) {
        HumanModule.shared.handleChallengeCancelledEvent();
    }

    @Override
    public void botDefenderChallengeRendered(@NonNull String appId) {

    }

    @Override
    public void botDefenderChallengeRenderFailed(@NonNull String appId) {

    }
}
```

### iOS

**Objective-C / AppDelegate.h:**

```objectivec
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
@import HUMAN;

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, HSBotDefenderDelegate>

@property (nonatomic, strong) UIWindow *window;

@end
```

**Objective-C / AppDelegate.m:**

```objectivec
#import "AppDelegate.h"
#import "HumanModule.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTAppSetupUtils.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    HSPolicy *policy = [[HSPolicy alloc] init];
    policy.automaticInterceptorPolicy.interceptorType = HSAutomaticInterceptorTypeNone;

    NSError *error = nil;
    [HumanSecurity startWithAppId:@"<APP_ID>" policy:policy error:&error];
    HumanSecurity.BD.delegate = self;
    if (error != nil) {
        NSLog(@"error: %@", error);
    }

    [[HumanModule shared] startObserving];

    // Configure React Native...

    return YES;
}

// MARK: - HSBotDefenderDelegate

- (void)botDefenderRequestBlockedWithUrl:(NSURL *)url appId:(NSString *)appId {

}

- (void)botDefenderChallengeSolvedForAppId:(NSString *)appId {
    [[HumanModule shared] handleChallengeSolvedEvent];
}

- (void)botDefenderChallengeCancelledForAppId:(NSString *)appId {
    [[HumanModule shared] handleChallengeCancelledEvent];
}

- (void)botDefenderDidUpdateHeadersWithHeaders:(NSDictionary<NSString *,NSString *> *)headers forAppId:(NSString *)appId {
    [[HumanModule shared] handleUpdatedHeaders:headers];
}

- (void)botDefenderChallengeRenderedForAppId:(NSString *)appId {

}

- (void)botDefenderChallengeRenderFailedForAppId:(NSString *)appId {

}

@end
```

Don't forget to change the `<APP_ID>` to your own AppID.

Let's talk about what we have in the code here:

1. We start the SDK as soon as possible and on the main thread.
2. We create a `HSPolicy` instance to configure the SDK's behavior. Here, we set the `interceptorType` property to `HSAutomaticInterceptorType.NONE`. This disables the automatic interception feature, which is not supported in React Native.
3. We call the `HumanSecurity.start(appId, policy)` function of the SDK with:
   * The `Application` instance (Android only).
   * Your AppID.
   * The policy object.
4. On iOS, we call the `startObserving` function on the `HumanModule`.

**Note:** If your app communicates with multiple servers with different AppIDs, you can call the `HumanSecurity.start(appIds, policy)` function to pass an array of AppIDs. Specify the relevant AppID for each API call in the SDK.

## How to add HTTP headers and handle block response

In your JavaScript code, you should:

1. Import the `HumanModule`.
2. Create a new native event emitter.
3. Add a listener to the "New Headers" event.
4. Add a listener to the "Challenge Result" event.
5. Add the SDK's HTTP headers to your URL requests.
6. When a request is blocked, send the block response to the SDK.
7. Handle the challenge's result.

**JavaScript:**

```javascript
/* Import HumanModule */
import { NativeModules, NativeEventEmitter } from 'react-native';
const { HumanModule } = NativeModules;

/* Create a new native event emitter */
const humanEventEmitter = new NativeEventEmitter(HumanModule);

/* Add listener to the "New Headers" event and store the SDK's HTTP headers in a variable */
let humanHttpHeaders = null;
const onNewHeaders = headers => {
  const obj = JSON.parse(headers);
  console.log(`Got new HTTP headers: ${JSON.stringify(obj)}`);
  humanHttpHeaders = obj;
};
const subscriptionHumanNewHeaders = humanEventEmitter.addListener('HumanNewHeaders', onNewHeaders);

/* Add listener to the "Challenge Result" event */
const onChallengeResult = result => {
  if (result === 'solved') {
    console.log('Challenge solved');
  } else if (result === 'cancelled') {
    console.log('Challenge cancelled');
  }
};
const subscriptionHumanChallengeResult = humanEventEmitter.addListener('HumanChallengeResult', onChallengeResult);

/* Get Human's HTTP headers */
const getHumanHeaders = async () => {
  if (humanHttpHeaders != null) {
    return humanHttpHeaders;
  }
  const headers = await HumanModule.getHTTPHeaders();
  const obj = JSON.parse(headers);
  console.log(`[HUMAN] Got HTTP headers: ${JSON.stringify(obj)}`);
  humanHttpHeaders = obj;
  return obj;
};

/* Send URL request */
async function sendRequest(url) {
  /* Get Human's HTTP headers */
  const humanHeaders = await getHumanHeaders();

  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: humanHeaders,
    });

    const json = await response.json();

    /* Send the response to the SDK */
    const result = await HumanModule.handleResponse(JSON.stringify(json), url);
    if (result === 'solved') {
      console.log('Challenge solved');
      await sendRequest(url);
    } else if (result === 'cancelled') {
      console.log('Challenge cancelled');
    } else if (result === 'false') {
      console.log('Request was not blocked by Human');
    }
  } catch (error) {
    console.error(error);
  }
}
```

## Understanding the block response

1. The HUMAN's Enforcer, when it decides to block a request, returns a JSON string in the response's body with an HTTP status code of 403. Here is an example of the response:

   ```json
   {
     "vid": "928d7ab3-9cf1-11ee-a624-b802520f369f",
     "uuid": "fd01e6d6-9cf2-11ee-808c-acde48001122",
     "page": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPgogICAgPHRpdGxlPkFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkLjwvdGl0bGU+CiAgICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9T3BlbitTYW5zOjMwMCIgcmVsPSJzdHlsZXNoZWV0Ij4KICAgIDxzdHlsZT4KICAgICAgICBodG1sLCBib2R5IHsKICAgICAgICAgICAgbWFyZ2luOiAwOwogICAgICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgICAgICBmb250LWZhbWlseTogJ09wZW4gU2FucycsIHNhbnMtc2VyaWY7CiAgICAgICAgICAgIGNvbG9yOiAjMDAwOwogICAgICAgIH0KCiAgICAgICAgYSB7CiAgICAgICAgICAgIGNvbG9yOiAjYzVjNWM1OwogICAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyIHsKICAgICAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICAgICAgZmxleDogMTsKICAgICAgICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyID4gZGl2IHsKICAgICAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAgIH0KCiAgICAgICAgLmNvbnRhaW5lciA+IGRpdiA+IGRpdiB7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIHdpZHRoOiA4MCU7CiAgICAgICAgfQoKICAgICAgICAuY3VzdG9tZXItbG9nby13cmFwcGVyIHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDJyZW07CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsKICAgICAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgIH0KCiAgICAgICAgLmN1c3RvbWVyLWxvZ28gewogICAgICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgIzAwMDsKICAgICAgICB9CgogICAgICAgIC5jdXN0b21lci1sb2dvID4gaW1nIHsKICAgICAgICAgICAgcGFkZGluZy1ib3R0b206IDFyZW07CiAgICAgICAgICAgIG1heC1oZWlnaHQ6IDUwcHg7CiAgICAgICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgICB9CgogICAgICAgIC5wYWdlLXRpdGxlLXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDI7CiAgICAgICAgfQoKICAgICAgICAucGFnZS10aXRsZSB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW4tcmV2ZXJzZTsKICAgICAgICB9CgogICAgICAgIC5jb250ZW50LXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDU7CiAgICAgICAgfQoKICAgICAgICAuY29udGVudCB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgfQoKICAgICAgICAucGFnZS1mb290ZXItd3JhcHBlciB7CiAgICAgICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMC4yOwogICAgICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgICAgICAgICBjb2xvcjogI2M1YzVjNTsKICAgICAgICAgICAgZm9udC1zaXplOiA3MCU7CiAgICAgICAgfQoKICAgICAgICBAbWVkaWEgKG1pbi13aWR0aDogNzY4cHgpIHsKICAgICAgICAgICAgaHRtbCwgYm9keSB7CiAgICAgICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICA8L3N0eWxlPgogICAgPCEtLSBDdXN0b20gQ1NTIC0tPgo8L2hlYWQ+Cgo8Ym9keT4KPHNlY3Rpb24gY2xhc3M9ImNvbnRhaW5lciI+CiAgICA8ZGl2IGNsYXNzPSJjdXN0b21lci1sb2dvLXdyYXBwZXIiPgogICAgICAgIDxkaXYgY2xhc3M9ImN1c3RvbWVyLWxvZ28iPgogICAgICAgICAgICA8aW1nIHNyYz0iIiBhbHQ9IkxvZ28iLz4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS10aXRsZS13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJwYWdlLXRpdGxlIj4KICAgICAgICAgICAgPGgxPlBsZWFzZSB2ZXJpZnkgeW91IGFyZSBhIGh1bWFuPC9oMT4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGVudC13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50Ij4KICAgICAgICAgICAgPGRpdiBpZD0icHgtY2FwdGNoYSI+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIEFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkIGJlY2F1c2Ugd2UgYmVsaWV2ZSB5b3UgYXJlIHVzaW5nIGF1dG9tYXRpb24gdG9vbHMgdG8gYnJvd3NlIHRoZQogICAgICAgICAgICAgICAgd2Vic2l0ZS4KICAgICAgICAgICAgPC9wPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFRoaXMgbWF5IGhhcHBlbiBhcyBhIHJlc3VsdCBvZiB0aGUgZm9sbG93aW5nOgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgICAgICBKYXZhc2NyaXB0IGlzIGRpc2FibGVkIG9yIGJsb2NrZWQgYnkgYW4gZXh0ZW5zaW9uIChhZCBibG9ja2VycyBmb3IgZXhhbXBsZSkKICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICAgICAgWW91ciBicm93c2VyIGRvZXMgbm90IHN1cHBvcnQgY29va2llcwogICAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgPC91bD4KICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBQbGVhc2UgbWFrZSBzdXJlIHRoYXQgSmF2YXNjcmlwdCBhbmQgY29va2llcyBhcmUgZW5hYmxlZCBvbiB5b3VyIGJyb3dzZXIgYW5kIHRoYXQgeW91IGFyZSBub3QgYmxvY2tpbmcKICAgICAgICAgICAgICAgIHRoZW0gZnJvbSBsb2FkaW5nLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgICAgUmVmZXJlbmNlIElEOiAjZmQwMWU2ZDYtOWNmMi0xMWVlLTgwOGMtYWNkZTQ4MDAxMTIyCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXItd3JhcHBlciI+CiAgICAgICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXIiPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFBvd2VyZWQgYnkKICAgICAgICAgICAgICAgIDxhIGhyZWY9Imh0dHBzOi8vd3d3LnBlcmltZXRlcnguY29tL3doeXdhc2libG9ja2VkIj5QZXJpbWV0ZXJYPC9hPgogICAgICAgICAgICAgICAgLCBJbmMuCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2Pgo8L3NlY3Rpb24+CjwhLS0gUHggLS0+CjxzY3JpcHQ+CiAgICB3aW5kb3cuX3B4QXBwSWQgPSAnUFhqOXk0UThFbSc7CiAgICB3aW5kb3cuX3B4SnNDbGllbnRTcmMgPSAnLy9jbGllbnQucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9tYWluLm1pbi5qcyc7CiAgICB3aW5kb3cuX3B4Rmlyc3RQYXJ0eUVuYWJsZWQgPSBmYWxzZTsKICAgIHdpbmRvdy5fcHhWaWQgPSAnOTI4ZDdhYjMtOWNmMS0xMWVlLWE2MjQtYjgwMjUyMGYzNjlmJzsKICAgIHdpbmRvdy5fcHhVdWlkID0gJ2ZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMic7CiAgICB3aW5kb3cuX3B4SG9zdFVybCA9ICcvL2NvbGxlY3Rvci1QWGo5eTRROEVtLnBlcmltZXRlcngubmV0JzsKPC9zY3JpcHQ+CjwhLS0gQ2FwdGNoYSAtLT4KPHNjcmlwdD4KICAgIHZhciBzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7CiAgICBzLnNyYyA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnOwogICAgdmFyIHAgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdOwogICAgcC5pbnNlcnRCZWZvcmUocywgbnVsbCk7CiAgICBpZiAoZmFsc2UpIHsKICAgICAgICBzLm9uZXJyb3IgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgIHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsKICAgICAgICAgICAgdmFyIHN1ZmZpeEluZGV4ID0gJy8vY2FwdGNoYS5wZXJpbWV0ZXJ4Lm5ldC9QWGo5eTRROEVtL2NhcHRjaGEuanM/YT1jJm09MSZ1PWZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMiZ2PTkyOGQ3YWIzLTljZjEtMTFlZS1hNjI0LWI4MDI1MjBmMzY5ZicuaW5kZXhPZignY2FwdGNoYS5qcycpOwogICAgICAgICAgICB2YXIgdGVtcGVyZWRCbG9ja1NjcmlwdCA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnLnN1YnN0cmluZyhzdWZmaXhJbmRleCk7CiAgICAgICAgICAgIHMuc3JjID0gJy8vY2FwdGNoYS5weC1jZG4ubmV0L1BYajl5NFE4RW0vJyArIHRlbXBlcmVkQmxvY2tTY3JpcHQ7CiAgICAgICAgICAgIHAucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUocywgcCk7CiAgICAgICAgfTsKICAgIH0KPC9zY3JpcHQ+CjwhLS0gQ3VzdG9tIFNjcmlwdCAtLT4KPC9ib2R5Pgo8L2h0bWw+Cg==",
     "appId": "PXj9y4Q8Em",
     "action": "captcha",
     "collectorUrl": "https://collector-pxj9y4q8em.perimeterx.net"
   }
   ```

2. The JSON contains metadata for the SDK.

3. Your app should pass the whole JSON to the SDK via the `HSBotDefender/handleBlockResponse` function. Otherwise, the SDK won't present a challenge to the user.