React Native Integration (Legacy)

The SDK can be integrated into a React Native project.

This integration is the legacy version. For the most recent version, see our recommended integration.

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 called HumanModule.

Add a package declaration at the top of each Java file matching your project’s package name (e.g. package com.yourapp;).

Java / HumanModule.java:

1import androidx.annotation.NonNull;
2import com.facebook.react.bridge.Promise;
3import com.facebook.react.bridge.ReactApplicationContext;
4import com.facebook.react.bridge.ReactContextBaseJavaModule;
5import com.facebook.react.bridge.ReactMethod;
6import com.facebook.react.modules.core.DeviceEventManagerModule;
7import com.humansecurity.mobile_sdk.HumanSecurity;
8import com.humansecurity.mobile_sdk.main.HSBotDefenderChallengeResult;
9import org.json.JSONObject;
10import java.util.HashMap;
11
12public class HumanModule extends ReactContextBaseJavaModule {
13
14 static HumanModule shared = null;
15
16 String strNewHeaders = "HumanNewHeaders";
17 String strChallengeResult = "HumanChallengeResult";
18 String strSolved = "solved";
19 String strCancelled = "cancelled";
20 String strFalse = "false";
21
22 HumanModule(ReactApplicationContext context) {
23 super(context);
24 }
25
26 @NonNull
27 @Override
28 public String getName() {
29 return "HumanModule";
30 }
31
32 public void handleUpdatedHeaders(HashMap<String, String> headers) {
33 if (!this.getReactApplicationContext().hasCatalystInstance()) {
34 return;
35 }
36 JSONObject json = new JSONObject(headers);
37 this.getReactApplicationContext()
38 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
39 .emit(strNewHeaders, json.toString());
40 }
41
42 public void handleChallengeSolvedEvent() {
43 if (!this.getReactApplicationContext().hasCatalystInstance()) {
44 return;
45 }
46 this.getReactApplicationContext()
47 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
48 .emit(strChallengeResult, strSolved);
49 }
50
51 public void handleChallengeCancelledEvent() {
52 if (!this.getReactApplicationContext().hasCatalystInstance()) {
53 return;
54 }
55 this.getReactApplicationContext()
56 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
57 .emit(strChallengeResult, strCancelled);
58 }
59
60 @ReactMethod
61 public void addListener(String eventName) {
62 }
63
64 @ReactMethod
65 public void removeListeners(Integer count) {
66 }
67
68 @ReactMethod
69 public void getHTTPHeaders(Promise promise) {
70 JSONObject json = new JSONObject(HumanSecurity.INSTANCE.getBD().headersForURLRequest(null));
71 promise.resolve(json.toString());
72 }
73
74 @ReactMethod
75 public void handleResponse(String response, Integer code, String url, Promise promise) {
76 boolean handled = HumanSecurity.INSTANCE.getBD().handleResponse(response, result -> {
77 promise.resolve(result == HSBotDefenderChallengeResult.SOLVED ? strSolved : strCancelled);
78 return null;
79 });
80 if (!handled) {
81 promise.resolve(strFalse);
82 }
83 }
84}
  1. Add the HumanModule to the HumanPackage and set the shared property.

Java / HumanPackage.java:

1import com.facebook.react.ReactPackage;
2import com.facebook.react.bridge.NativeModule;
3import com.facebook.react.bridge.ReactApplicationContext;
4import com.facebook.react.uimanager.ViewManager;
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.List;
8
9public class HumanPackage implements ReactPackage {
10
11 @Override
12 public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
13 return Collections.emptyList();
14 }
15
16 @Override
17 public List<NativeModule> createNativeModules(
18 ReactApplicationContext reactContext) {
19 List<NativeModule> modules = new ArrayList<>();
20
21 HumanModule module = new HumanModule(reactContext);
22 HumanModule.shared = module;
23 modules.add(module);
24
25 return modules;
26 }
27}

iOS

Create a native module called HumanModule.

Objective-C / HumanModule.h:

1#import <Foundation/Foundation.h>
2#import <React/RCTBridgeModule.h>
3#import <React/RCTEventEmitter.h>
4
5NS_ASSUME_NONNULL_BEGIN
6
7@interface HumanModule : RCTEventEmitter <RCTBridgeModule>
8
9+ (HumanModule *)shared;
10- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers;
11- (void)handleChallengeSolvedEvent;
12- (void)handleChallengeCancelledEvent;
13
14@end
15
16NS_ASSUME_NONNULL_END

Objective-C / HumanModule.m:

1#import "HumanModule.h"
2@import HUMAN;
3
4static NSString *strNewHeaders = @"HumanNewHeaders";
5static NSString *strChallengeResult = @"HumanChallengeResult";
6static NSString *strSolved = @"solved";
7static NSString *strCancelled = @"cancelled";
8static NSString *strFalse = @"false";
9
10@implementation HumanModule
11
12static HumanModule *shared = nil;
13
14+ (HumanModule *)shared {
15 return shared;
16}
17
18+ (BOOL)requiresMainQueueSetup {
19 return YES;
20}
21
22- (instancetype)init {
23 self = [super init];
24 shared = self;
25 return self;
26}
27
28- (NSArray<NSString *> *)supportedEvents {
29 return @[strNewHeaders, strChallengeResult];
30}
31
32- (void)handleUpdatedHeaders:(NSDictionary<NSString *,NSString *> *)headers {
33 NSData *data = [NSJSONSerialization dataWithJSONObject:headers options:0 error:nil];
34 NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
35 [self sendEventWithName:strNewHeaders body:json];
36}
37
38- (void)handleChallengeSolvedEvent {
39 [self sendEventWithName:strChallengeResult body:strSolved];
40}
41
42- (void)handleChallengeCancelledEvent {
43 [self sendEventWithName:strChallengeResult body:strCancelled];
44}
45
46RCT_EXPORT_MODULE(HumanModule);
47
48RCT_REMAP_METHOD(getHTTPHeaders,
49 resolver:(RCTPromiseResolveBlock)resolve
50 rejecter:(RCTPromiseRejectBlock)reject) {
51 NSDictionary<NSString *, NSString *> *humanHttpHeaders = [HumanSecurity.BD headersForURLRequestForAppId:nil];
52 NSData *data = [NSJSONSerialization dataWithJSONObject:humanHttpHeaders options:0 error:nil];
53 NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
54 resolve(@[json]);
55}
56
57RCT_REMAP_METHOD(handleResponse,
58 response:(NSString *)response
59 code:(NSInteger)code
60 url:(NSString *)url
61 resolver:(RCTPromiseResolveBlock)resolve
62 rejecter:(RCTPromiseRejectBlock)reject) {
63 NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
64 NSHTTPURLResponse *httpURLResponse = [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:url] statusCode:code HTTPVersion:nil headerFields:nil];
65 BOOL handled = [HumanSecurity.BD handleResponseWithResponse:httpURLResponse data:data callback:^(enum HSBotDefenderChallengeResult result) {
66 resolve((result == HSBotDefenderChallengeResultSolved ? strSolved : strCancelled));
67 }];
68 if (!handled) {
69 resolve(strFalse);
70 }
71}
72
73@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 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:

1import android.app.Application;
2import java.util.HashMap;
3import java.util.List;
4import androidx.annotation.NonNull;
5import androidx.annotation.Nullable;
6import com.facebook.react.PackageList;
7import com.facebook.react.ReactApplication;
8import com.facebook.react.ReactNativeHost;
9import com.facebook.react.ReactPackage;
10import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
11import com.facebook.react.defaults.DefaultReactNativeHost;
12import com.facebook.react.soloader.OpenSourceMergedSoMapping;
13import com.facebook.soloader.SoLoader;
14import com.humansecurity.mobile_sdk.HumanSecurity;
15import com.humansecurity.mobile_sdk.main.HSBotDefenderDelegate;
16import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType;
17import com.humansecurity.mobile_sdk.main.policy.HSPolicy;
18
19public class MainApplication extends Application implements ReactApplication, HSBotDefenderDelegate {
20
21 private final ReactNativeHost mReactNativeHost =
22 new DefaultReactNativeHost(this) {
23 @Override
24 public boolean getUseDeveloperSupport() {
25 return BuildConfig.DEBUG;
26 }
27
28 @Override
29 protected List<ReactPackage> getPackages() {
30 List<ReactPackage> packages = new PackageList(this).getPackages();
31 packages.add(new HumanPackage());
32 return packages;
33 }
34
35 @Override
36 protected String getJSMainModuleName() {
37 return "index";
38 }
39
40 @Override
41 protected boolean isNewArchEnabled() {
42 return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
43 }
44
45 @Override
46 protected Boolean isHermesEnabled() {
47 return BuildConfig.IS_HERMES_ENABLED;
48 }
49 };
50
51 @Override
52 public ReactNativeHost getReactNativeHost() {
53 return mReactNativeHost;
54 }
55
56 @Override
57 public void onCreate() {
58 super.onCreate();
59 try {
60 SoLoader.init(this, OpenSourceMergedSoMapping.INSTANCE);
61 } catch (Exception e) {
62 throw new RuntimeException(e);
63 }
64 if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
65 DefaultNewArchitectureEntryPoint.load();
66 }
67
68 HSPolicy policy = new HSPolicy();
69 policy.getAutomaticInterceptorPolicy().setInterceptorType(HSAutomaticInterceptorType.NONE);
70 try {
71 HumanSecurity.INSTANCE.start(this, "<APP_ID>", policy);
72 HumanSecurity.INSTANCE.getBD().setDelegate(this);
73 } catch (Exception exception) {
74 exception.printStackTrace();
75 }
76 }
77
78 @Override
79 public void botDefenderDidUpdateHeaders(@NonNull HashMap<String, String> headers, @NonNull String appId) {
80 if (HumanModule.shared != null) {
81 HumanModule.shared.handleUpdatedHeaders(headers);
82 }
83 }
84
85 @Override
86 public void botDefenderRequestBlocked(@Nullable String url, @NonNull String appId) {
87 }
88
89 @Override
90 public void botDefenderChallengeSolved(@NonNull String appId) {
91 HumanModule.shared.handleChallengeSolvedEvent();
92 }
93
94 @Override
95 public void botDefenderChallengeCancelled(@NonNull String appId) {
96 HumanModule.shared.handleChallengeCancelledEvent();
97 }
98
99 @Override
100 public void botDefenderChallengeRendered(@NonNull String appId) {
101 }
102
103 @Override
104 public void botDefenderChallengeRenderFailed(@NonNull String appId) {
105 }
106}

iOS

Objective-C / AppDelegate.h:

1#import <RCTAppDelegate.h>
2#import <UIKit/UIKit.h>
3@import HUMAN;
4
5@interface AppDelegate : RCTAppDelegate <HSBotDefenderDelegate>
6
7@end

Objective-C / AppDelegate.m:

1#import "AppDelegate.h"
2#import "HumanModule.h"
3
4#import <React/RCTBundleURLProvider.h>
5
6@implementation AppDelegate
7
8- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
9{
10 self.moduleName = @"<YOUR_APP_NAME>";
11 self.initialProps = @{};
12
13 HSPolicy *policy = [[HSPolicy alloc] init];
14 policy.automaticInterceptorPolicy.interceptorType = HSAutomaticInterceptorTypeNone;
15 NSError *error = nil;
16 [HumanSecurity startWithAppId:@"<APP_ID>" policy:policy error:&error];
17 HumanSecurity.BD.delegate = self;
18 if (error != nil) {
19 NSLog(@"failed to start. error: %@", error.localizedDescription);
20 }
21
22 BOOL result = [super application:application didFinishLaunchingWithOptions:launchOptions];
23
24 [[HumanModule shared] startObserving];
25
26 return result;
27}
28
29- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
30{
31 return [self bundleURL];
32}
33
34- (NSURL *)bundleURL
35{
36#if DEBUG
37 return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
38#else
39 return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
40#endif
41}
42
43// MARK: - HSBotDefenderDelegate
44
45- (void)botDefenderDidUpdateHeaders:(NSDictionary<NSString *,NSString *> *)headers forAppId:(NSString *)appId {
46 if ([HumanModule shared] != nil) {
47 [[HumanModule shared] handleUpdatedHeaders:headers];
48 }
49}
50
51- (void)botDefenderChallengeSolvedForAppId:(NSString *)appId {
52 [[HumanModule shared] handleChallengeSolvedEvent];
53}
54
55- (void)botDefenderChallengeCancelledForAppId:(NSString *)appId {
56 [[HumanModule shared] handleChallengeCancelledEvent];
57}
58
59- (void)botDefenderChallengeRenderedForAppId:(NSString *)appId {
60}
61
62- (void)botDefenderChallengeRenderFailedForAppId:(NSString *)appId {
63}
64
65@end

Don’t forget to change the <APP_ID> to your own AppID and <YOUR_APP_NAME> to your React Native module name (as registered in AppRegistry).

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.
  5. On Android, we use SoLoader.init(this, OpenSourceMergedSoMapping.INSTANCE) for proper native library loading (required for React Native 0.73+).
  6. On iOS, AppDelegate extends RCTAppDelegate (required for React Native 0.73+). Set self.moduleName and self.initialProps, then call [super application:application didFinishLaunchingWithOptions:launchOptions] to configure React Native.

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:

1/* Import HumanModule */
2import { NativeModules, NativeEventEmitter } from 'react-native';
3const { HumanModule } = NativeModules;
4
5/* Create a new native event emitter */
6const humanEventEmitter = new NativeEventEmitter(HumanModule);
7
8/* Add listener to the "New Headers" event and store the SDK's HTTP headers in a variable */
9let humanHttpHeaders = null;
10const onNewHeaders = headers => {
11 const obj = JSON.parse(headers);
12 console.log(`Got new HTTP headers: ${JSON.stringify(obj)}`);
13 humanHttpHeaders = obj;
14};
15const subscriptionHumanNewHeaders = humanEventEmitter.addListener('HumanNewHeaders', onNewHeaders);
16
17/* Add listener to the "Challenge Result" event */
18const onChallengeResult = result => {
19 if (result === 'solved') {
20 console.log('Challenge solved');
21 } else if (result === 'cancelled') {
22 console.log('Challenge cancelled');
23 }
24};
25const subscriptionHumanChallengeResult = humanEventEmitter.addListener('HumanChallengeResult', onChallengeResult);
26
27/* Get Human's HTTP headers */
28const getHumanHeaders = async () => {
29 if (humanHttpHeaders != null) {
30 return humanHttpHeaders;
31 }
32 const headers = await HumanModule.getHTTPHeaders();
33 const obj = JSON.parse(headers);
34 console.log(`[HUMAN] Got HTTP headers: ${JSON.stringify(obj)}`);
35 return obj;
36};
37
38/* Send URL request */
39async function sendRequest(url) {
40 /* Get Human's HTTP headers */
41 const humanHeaders = await getHumanHeaders();
42
43 try {
44 const response = await fetch(url, {
45 method: 'GET',
46 headers: humanHeaders,
47 });
48
49 const json = await response.json();
50
51 /* Send the response to the SDK */
52 const result = await HumanModule.handleResponse(JSON.stringify(json), response.status, url);
53 if (result === 'solved') {
54 console.log('Challenge solved');
55 await sendRequest(url);
56 } else if (result === 'cancelled') {
57 console.log('Challenge cancelled');
58 } else if (result === 'false') {
59 console.log('Request was not blocked by Human');
60 }
61 } catch (error) {
62 console.error(error);
63 }
64}

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:

    1{
    2 "vid": "928d7ab3-9cf1-11ee-a624-b802520f369f",
    3 "uuid": "fd01e6d6-9cf2-11ee-808c-acde48001122",
    4 "page": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPgogICAgPHRpdGxlPkFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkLjwvdGl0bGU+CiAgICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9T3BlbitTYW5zOjMwMCIgcmVsPSJzdHlsZXNoZWV0Ij4KICAgIDxzdHlsZT4KICAgICAgICBodG1sLCBib2R5IHsKICAgICAgICAgICAgbWFyZ2luOiAwOwogICAgICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgICAgICBmb250LWZhbWlseTogJ09wZW4gU2FucycsIHNhbnMtc2VyaWY7CiAgICAgICAgICAgIGNvbG9yOiAjMDAwOwogICAgICAgIH0KCiAgICAgICAgYSB7CiAgICAgICAgICAgIGNvbG9yOiAjYzVjNWM1OwogICAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyIHsKICAgICAgICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgICAgICAgICAgZGlzcGxheTogZmxleDsKICAgICAgICAgICAgZmxleDogMTsKICAgICAgICAgICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgICAgICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgfQoKICAgICAgICAuY29udGFpbmVyID4gZGl2IHsKICAgICAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIGp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAgIH0KCiAgICAgICAgLmNvbnRhaW5lciA+IGRpdiA+IGRpdiB7CiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgICAgICAgIHdpZHRoOiA4MCU7CiAgICAgICAgfQoKICAgICAgICAuY3VzdG9tZXItbG9nby13cmFwcGVyIHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDJyZW07CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsKICAgICAgICAgICAgdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgIH0KCiAgICAgICAgLmN1c3RvbWVyLWxvZ28gewogICAgICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgIzAwMDsKICAgICAgICB9CgogICAgICAgIC5jdXN0b21lci1sb2dvID4gaW1nIHsKICAgICAgICAgICAgcGFkZGluZy1ib3R0b206IDFyZW07CiAgICAgICAgICAgIG1heC1oZWlnaHQ6IDUwcHg7CiAgICAgICAgICAgIG1heC13aWR0aDogMTAwJTsKICAgICAgICB9CgogICAgICAgIC5wYWdlLXRpdGxlLXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDI7CiAgICAgICAgfQoKICAgICAgICAucGFnZS10aXRsZSB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW4tcmV2ZXJzZTsKICAgICAgICB9CgogICAgICAgIC5jb250ZW50LXdyYXBwZXIgewogICAgICAgICAgICBmbGV4LWdyb3c6IDU7CiAgICAgICAgfQoKICAgICAgICAuY29udGVudCB7CiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgICAgfQoKICAgICAgICAucGFnZS1mb290ZXItd3JhcHBlciB7CiAgICAgICAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgICAgICAgIGZsZXgtZ3JvdzogMC4yOwogICAgICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgICAgICAgICBjb2xvcjogI2M1YzVjNTsKICAgICAgICAgICAgZm9udC1zaXplOiA3MCU7CiAgICAgICAgfQoKICAgICAgICBAbWVkaWEgKG1pbi13aWR0aDogNzY4cHgpIHsKICAgICAgICAgICAgaHRtbCwgYm9keSB7CiAgICAgICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICA8L3N0eWxlPgogICAgPCEtLSBDdXN0b20gQ1NTIC0tPgo8L2hlYWQ+Cgo8Ym9keT4KPHNlY3Rpb24gY2xhc3M9ImNvbnRhaW5lciI+CiAgICA8ZGl2IGNsYXNzPSJjdXN0b21lci1sb2dvLXdyYXBwZXIiPgogICAgICAgIDxkaXYgY2xhc3M9ImN1c3RvbWVyLWxvZ28iPgogICAgICAgICAgICA8aW1nIHNyYz0iIiBhbHQ9IkxvZ28iLz4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS10aXRsZS13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJwYWdlLXRpdGxlIj4KICAgICAgICAgICAgPGgxPlBsZWFzZSB2ZXJpZnkgeW91IGFyZSBhIGh1bWFuPC9oMT4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGVudC13cmFwcGVyIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50Ij4KICAgICAgICAgICAgPGRpdiBpZD0icHgtY2FwdGNoYSI+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIEFjY2VzcyB0byB0aGlzIHBhZ2UgaGFzIGJlZW4gZGVuaWVkIGJlY2F1c2Ugd2UgYmVsaWV2ZSB5b3UgYXJlIHVzaW5nIGF1dG9tYXRpb24gdG9vbHMgdG8gYnJvd3NlIHRoZQogICAgICAgICAgICAgICAgd2Vic2l0ZS4KICAgICAgICAgICAgPC9wPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFRoaXMgbWF5IGhhcHBlbiBhcyBhIHJlc3VsdCBvZiB0aGUgZm9sbG93aW5nOgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDx1bD4KICAgICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgICAgICBKYXZhc2NyaXB0IGlzIGRpc2FibGVkIG9yIGJsb2NrZWQgYnkgYW4gZXh0ZW5zaW9uIChhZCBibG9ja2VycyBmb3IgZXhhbXBsZSkKICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgICAgICAgWW91ciBicm93c2VyIGRvZXMgbm90IHN1cHBvcnQgY29va2llcwogICAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgPC91bD4KICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICBQbGVhc2UgbWFrZSBzdXJlIHRoYXQgSmF2YXNjcmlwdCBhbmQgY29va2llcyBhcmUgZW5hYmxlZCBvbiB5b3VyIGJyb3dzZXIgYW5kIHRoYXQgeW91IGFyZSBub3QgYmxvY2tpbmcKICAgICAgICAgICAgICAgIHRoZW0gZnJvbSBsb2FkaW5nLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgICAgUmVmZXJlbmNlIElEOiAjZmQwMWU2ZDYtOWNmMi0xMWVlLTgwOGMtYWNkZTQ4MDAxMTIyCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXItd3JhcHBlciI+CiAgICAgICAgPGRpdiBjbGFzcz0icGFnZS1mb290ZXIiPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIFBvd2VyZWQgYnkKICAgICAgICAgICAgICAgIDxhIGhyZWY9Imh0dHBzOi8vd3d3LnBlcmltZXRlcnguY29tL3doeXdhc2libG9ja2VkIj5QZXJpbWV0ZXJYPC9hPgogICAgICAgICAgICAgICAgLCBJbmMuCiAgICAgICAgICAgIDwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2Pgo8L3NlY3Rpb24+CjwhLS0gUHggLS0+CjxzY3JpcHQ+CiAgICB3aW5kb3cuX3B4QXBwSWQgPSAnUFhqOXk0UThFbSc7CiAgICB3aW5kb3cuX3B4SnNDbGllbnRTcmMgPSAnLy9jbGllbnQucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9tYWluLm1pbi5qcyc7CiAgICB3aW5kb3cuX3B4Rmlyc3RQYXJ0eUVuYWJsZWQgPSBmYWxzZTsKICAgIHdpbmRvdy5fcHhWaWQgPSAnOTI4ZDdhYjMtOWNmMS0xMWVlLWE2MjQtYjgwMjUyMGYzNjlmJzsKICAgIHdpbmRvdy5fcHhVdWlkID0gJ2ZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMic7CiAgICB3aW5kb3cuX3B4SG9zdFVybCA9ICcvL2NvbGxlY3Rvci1QWGo5eTRROEVtLnBlcmltZXRlcngubmV0JzsKPC9zY3JpcHQ+CjwhLS0gQ2FwdGNoYSAtLT4KPHNjcmlwdD4KICAgIHZhciBzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7CiAgICBzLnNyYyA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnOwogICAgdmFyIHAgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdOwogICAgcC5pbnNlcnRCZWZvcmUocywgbnVsbCk7CiAgICBpZiAoZmFsc2UpIHsKICAgICAgICBzLm9uZXJyb3IgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgIHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsKICAgICAgICAgICAgdmFyIHN1ZmZpeEluZGV4ID0gJy8vY2FwdGNoYS5wZXJpbWV0ZXJ4Lm5ldC9QWGo5eTRROEVtL2NhcHRjaGEuanM/YT1jJm09MSZ1PWZkMDFlNmQ2LTljZjItMTFlZS04MDhjLWFjZGU0ODAwMTEyMiZ2PTkyOGQ3YWIzLTljZjEtMTFlZS1hNjI0LWI4MDI1MjBmMzY5ZicuaW5kZXhPZignY2FwdGNoYS5qcycpOwogICAgICAgICAgICB2YXIgdGVtcGVyZWRCbG9ja1NjcmlwdCA9ICcvL2NhcHRjaGEucGVyaW1ldGVyeC5uZXQvUFhqOXk0UThFbS9jYXB0Y2hhLmpzP2E9YyZtPTEmdT1mZDAxZTZkNi05Y2YyLTExZWUtODA4Yy1hY2RlNDgwMDExMjImdj05MjhkN2FiMy05Y2YxLTExZWUtYTYyNC1iODAyNTIwZjM2OWYnLnN1YnN0cmluZyhzdWZmaXhJbmRleCk7CiAgICAgICAgICAgIHMuc3JjID0gJy8vY2FwdGNoYS5weC1jZG4ubmV0L1BYajl5NFE4RW0vJyArIHRlbXBlcmVkQmxvY2tTY3JpcHQ7CiAgICAgICAgICAgIHAucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUocywgcCk7CiAgICAgICAgfTsKICAgIH0KPC9zY3JpcHQ+CjwhLS0gQ3VzdG9tIFNjcmlwdCAtLT4KPC9ib2R5Pgo8L2h0bWw+Cg==",
    5 "appId": "PXj9y4Q8Em",
    6 "action": "captcha",
    7 "collectorUrl": "https://collector-pxj9y4q8em.perimeterx.net"
    8}
  2. The JSON contains metadata for the SDK.

  3. Your app should pass the whole JSON to the SDK via the HSBotDefender/handleResponse(response:data:callback:) function. Otherwise, the SDK won’t present a challenge to the user.