Expo Integration

You can integrate the SDK into an Expo project. This article explains how to integrate and implement the HUMAN Expo SDK wrapper into an Expo app.

Integrate the HUMAN Expo model

In the terminal from the command line, run the following:

npm install @humansecurity/expo-mobile-sdk
npx expo prebuild

HUMAN Expo Mobile API

The HUMAN Security Expo Mobile API offers the following functions:

export declare class HumanSecurity {
    static sdkVersion(): string;
    static startWithAppId(appId: string, policy?: HSPolicy): Promise<void>;
    static startWithAppIds(appIds: string[], policy?: HSPolicy): Promise<void>;
    static vid(appId: string): string | null;
    static BD: {
        headersForURLRequest(appId?: string): {[key: string]: string};
        handleResponse(response: string, code: number, url: string): Promise<HSBotDefenderChallengeResult>;
        canHandleResponse(response: string, code: number, url: string): boolean;
        challengeReferenceId(): string;
        setCustomParameters(parameters: {[key: string]: string}, appId?: string): void;
        addListener(eventName: string, callback: (data: any) => void): import("expo-modules-core").Subscription;
    };
    static AD: {
        setUserId(userId: string | null, appId?: string): void;
        registerOutgoingUrlRequest(url: string, appId?: string): void;
        setAdditionalData(parameters: {[key: string]: string}, appId?: string): void;
    };
}

How to start the SDK

The most important step is to start the SDK as early as possible in your app flow. If your app sends a URL request to your server before the SDK starts, the attached headers won’t include the latest updated tokens. As a result, HUMAN’s Enforcer could block the request, and the SDK won’t be able to present a challenge to the user. Also, be sure to start the SDK only once.

To set it up, call startWithAppId in your Expo main entry point (index.tsx) or at the starting point of your app, where you register and initialize one-time services. It’s better to start the SDK outside of any UI component to avoid lifecycle issues.

import { HSPolicy, HumanSecurity } from "@humansecurity/expo-mobile-sdk";

// Initialize SDK once on app startup
(() => {
    HumanSecurity.startWithAppId("<APP_ID>")
    .then(() => {
        console.log("SDK started successfully");
    })
    .catch((error) => {
        console.error("Error starting the SDK:", error);
    });
})();

🚧

Warning

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

Policy options

Currently, the HSPolicy class includes two settings:

  1. hybridAppPolicy — Use this if your app operates in hybrid mode, meaning it has a WebView that loads a site also protected by HUMAN and running its sensor. In this case, configure all relevant website domains with the appID.
  2. detectionPolicy — This setting lets you configure whether the SDK can send touch and motion events, either touch only or both touch and motion, to the server to improve detection. By default, it is off, and you can choose to enable it.

For example:

import { HSPolicy, HumanSecurity } from "@humansecurity/expo-mobile-sdk";

// Initialize SDK once on app startup
(() => {
    const policy: HSPolicy = {
        hybridAppPolicy: {
             webRootDomains: {
                 APP_ID: ["my-domain.com"],
             },
           },
        detectionPolicy: {
           allowTouchDetection: true,
           allowDeviceMotionDetection: true,
         },
    };

    HumanSecurity.startWithAppId("<APP_ID>", policy)
    .then(() => {
        console.log("SDK started successfully");
    })
    .catch((error) => {
        console.error("Error starting the SDK:", error);
    });
})();

Here’s what the code includes:

  1. We start the SDK as soon as possible and only once. (You can move this code to a different function/file and export it, as long as it’s called only once.)
  2. The startWithAppId function of the SDK is called with the following parameters::
    • Your appID.
    • The policy object, if needed.
  3. In the second example, an HSPolicy instance configures the SDK’s behavior. This object defines settings such as hybridAppPolicy.webRootDomains, indicating that the app uses WebViews containing pages from a domain also protected by HUMAN Security, which is considered hybrid mode. Additionally, detectionPolicy is configured to send touch and motion data. If these parameters aren’t needed, you can simply omit the policy.

📘

Note

If your app communicates with multiple servers that use different appIDs, you can call HumanSecurity/startWithAppIds(appIds: string[], policy?: HSPolicy): Promise<void>. This lets you to pass an array of appIDs. In this case, you must specify the relevant appID for each API call to the SDK. If you have only one appID, providing it in most functions is optional.

Bot Defender Integration

Add the SDK’s HTTP headers to your URL requests and handle blocked requests

  • The SDK provides HTTP headers that your app must add to URL requests. It’s essential to include these headers in every request.
  • You shouldn’t cache these HTTP headers. They contain a token with an expiration date, and the SDK keeps it up to date. Ensure the code isn’t cached so that each request triggers a call to the HumanSecurity module.
  • The SDK can handle the blocked request and present a challenge to the user.
  • Ensure that arguments are passed correctly to the wrapper. For example, when using axios, you might need to call JSON.stringify(error.response?.data) before passing the response body to the SDK. Additionally, in Axios, the catch block handles blocked responses because it indicates a failed request.

The following are examples using either a fetch API or using axios.

import HumanSecurity, { HSBotDefenderChallengeResult } from "@humansecurity/expo-mobile-sdk";


static async makeApiCall(url: string): Promise<void> {
    try {
        const headers = HumanSecurity.BD.headersForURLRequest(HumanSecurityManager.appId);

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

        const statusCode = response.status;
        if (statusCode !== 200) {
            const responseText = await response.text();
            const canHandle = HumanSecurity.BD.canHandleResponse(responseText, statusCode, url);

            if (canHandle) {
                HumanSecurity.BD.handleResponse(responseText, statusCode, url)
                  .then((result) => {
                    if (result === HSBotDefenderChallengeResult.SOLVED) {
                      console.log("Challenge solved successfully! Retrying request...");
                    } else {
                      console.log("Challenge was cancelled, or failed.");
                    }
                  })
                  .catch((error) => {
                    console.error("Error handling response:", error);
                  });
            } else {
                console.log("Response cannot be handled by the library.");
            }
        } else {
            console.log(`API Call Successful with status ${statusCode}`);
        }
    } catch (error) {
        console.error("Error making API call:", error);
    }
}
import { HumanSecurity, HSBotDefenderChallengeResult } from "@humansecurity/expo-mobile-sdk";
import axios from "axios";

static async makeApiCallWithAxios(url: string): Promise<void> {
    try {
        // Get headers from HumanSecurity
        const headers = HumanSecurity.BD.headersForURLRequest(HumanSecurityManager.appId);
        const response = await axios.get(url, {
            headers,
        });

        // Do whatever you do with the response.
        console.log(`API Call Successful with response ${JSON.stringify(response.status)}`);
    } catch (error: any) {
        console.log("Error making API call:", error);

        // Ensure response exists before accessing status
        const statusCode = error.response?.status || 0;
        const responseText = error.response?.data || "No response data";

        console.log(`API Call failed with status: ${statusCode}`);
        console.log("Response Text: " + JSON.stringify(responseText));

        // Check if HumanSecurity can handle the response
        const canHandle = HumanSecurity.BD.canHandleResponse(JSON.stringify(responseText), statusCode, url)
        if (canHandle) {
            HumanSecurity.BD.handleResponse(
              JSON.stringify(responseText),
              statusCode,
              url
            )
            .then((result) => {
              console.log(`Challenge result: ${HSBotDefenderChallengeResult[result]}`);
            })
            .catch((handleError) => {
              console.error("Error handling response:", handleError);
            });
        } else {
            console.log("Response cannot be handled by the library.");
        }
    }
}

Here’s what the code includes:

  1. The SDK provides HTTP headers.
  2. The HTTP headers are added to the URL requests.
  3. The URL request is sent.
  4. In case of an error, the response is sent to the SDK, which checks if it’s a blocked request by calling canHandleResponse. If canHandleResponse returns true, handleResponse is called to present the challenge to the user.
  5. The challenge result HSBotDefenderChallengeResult can be one of the following:
    • SOLVED – The user successfully completed the challenge.
    • CANCELLED – The user dismissed or failed to complete the challenge.
    • FAILED – The SDK was unable to handle the response, usually indicating that something wasn't passed correctly to the handleResponse function.

Note that you can await handleResponse, but in this case, you would be waiting until the user solves the challenge. You can also continue your flow and handle the result in the .then block, as shown in the example.

What to do when a request is blocked

  • Handle it as a failure. Your app should treat the blocked request as a failure. However, keep in mind that your app’s UI will be shown again after the user has solved or cancelled the challenge. If the request was triggered by a user action, you should make it clear that the user may try again.
  • Use the promise result to write analytics, logs, etc.
  • You may use the returned promise to retry the original request when appropriate. Consider the following:
    • The promise returns out of the original request’s scope. Make sure you handle that case correctly.
    • The user might cancel the challenge. In this case, you shouldn’t retry the request because it would be blocked again.
    • The retry attempt might fail. Don’t assume it will be successful.

Understanding the block response

  1. HUMAN's Enforcer, when it decides to block a request, returns a JSON string in the response body with status code 403. For example:
{
  "vid": "928d7ab3-9cf1-11ee-a624-b802520f369f",
  "uuid": "fd01e6d6-9cf2-11ee-808c-acde48001122",
  "page": "...",
  "appId": "PXj9y4Q8Em",
  "action": "captcha",
  "collectorUrl": "https://collector-pxj9y4q8em.perimeterx.net"
}
  1. The JSON contains metadata for the SDK.
  2. Your app should pass the entire JSON as response in a string format, along with the status code and URL, to the SDK via the following. Otherwise, the SDK won’t present a challenge to the user.
HumanSecurity.BD.handleResponse(response: string, code: number, url: string): Promise<HSBotDefenderChallengeResult>

Set Custom Parameters for Bot Defender (Optional)

You can set custom parameters to configure HUMAN's backend with additional values. To do so:

  • Set those parameters as an object in JavaScript using keys in the format custom_param_[x], where [x] is a number between 1 and 10.
  • After calling startWithAppId, call setCustomParameters.

For example:

import { HumanSecurity } from "@humansecurity/expo-mobile-sdk";

function setCustomParametersForBotDefender() {
    const parameters = { custom_param_1: "value1", custom_param_2: "value2" };
    try {
        HumanSecurity.BD.setCustomParameters(parameters, "<APP_ID>");
        console.log("Custom Parameters Set Successfully");
    } catch (error) {
        console.log("Failed to Set Custom Parameters");
    }
}

Bot Defender Delegates

Bot Defender provides several delegates, or callbacks, to notify about events related to headers and challenges. These callbacks are mainly intended for analytics and statistics rather than for taking specific actions. In the Expo library, these callbacks work as events.

To listen to a specific event, you must add a listener for it. When the event triggers, you can log or handle the result accordingly. Be sure to remove the listener when leaving the context to avoid memory leaks.

Below is an example implementation for listening to all events and also how to handle a single specific event.

import { HumanSecurity, HS_EVENTS } from "@humansecurity/expo-mobile-sdk";
import { useEffect } from "react";

useEffect(() => {
  const eventListeners = Object.values(HS_EVENTS).map((eventName) => {
    console.log(`Registering listener for event: ${eventName}`);
    return HumanSecurity.BD.addListener(eventName, (data) => {
      handleEvent(eventName, data);
    });
  });

  return () => {
    eventListeners.forEach((subscription) => subscription.remove());
  };
}, []);

const handleEvent = (eventName: string, data: any) => {
  console.log(`Event Received: ${eventName}`, data);
  // This is the full list of events.
  switch (eventName) {
    case HS_EVENTS.BOT_DEFENDER_REQUEST_BLOCKED:
      console.log("Request Blocked");
      break;

    case HS_EVENTS.BOT_DEFENDER_CHALLENGE_SOLVED:
      console.log("Challenge Solved");
      break;

    case HS_EVENTS.BOT_DEFENDER_CHALLENGE_CANCELLED:
      console.log("Challenge Cancelled");
      break;

    case HS_EVENTS.BOT_DEFENDER_CHALLENGE_RENDERED:
      console.log("Challenge Rendered");
      break;

    case HS_EVENTS.BOT_DEFENDER_CHALLENGE_RENDER_FAILED:
      console.log("Challenge Render Failed");
      break;

    case HS_EVENTS.BOT_DEFENDER_DID_UPDATE_HEADERS:
      console.log("Headers Were Updated");
      break;

    default:
      console.log("Unknown event received:", eventName);
      break;
  }
};

Listening to a Single Event

If you only want to listen to a single specific event, you can do the following:

useEffect(() => {
  const specificEvent = HS_EVENTS.BOT_DEFENDER_CHALLENGE_SOLVED;
  console.log(`Registering listener for event: ${specificEvent}`);

  const eventListener = HumanSecurity.BD.addListener(specificEvent, (data) => {
    // Handle the specific event
    console.log(`Specific Event Received: ${specificEvent}`, data);
  });

  // Clean up the listener when leaving the context
  return () => {
    eventListener.remove();
  };
}, []);

Account Defender Integration

Enable Account Defender in your app

In order to enable Account Defender, you should set the UserID of your current logged-in user in the SDK. When the user logs out, make sure to call the function with null to clear the user ID. For example:

import { HumanSecurity } from "@humansecurity/expo-mobile-sdk";

function onUserLoggedIn(userID) {
  try {
    HumanSecurity.AD.setUserId(userID, "<APP_ID>");
  } catch (error) {
    console.log(`Exception: ${error.message}`);
  }
}

function onUserLoggedOut() {
  try {
    HumanSecurity.AD.setUserId(null, "<APP_ID>");
  } catch (error) {
    console.log(`Exception: ${error.message}`);
  }
}

Notify HUMAN's backend on outgoing URL requests from the app

In order to allow Account Defender to protect the user's account, your app must provide the SDK with outgoing URL requests.
This should be done after you set the user id.

import { HumanSecurity } from "@humansecurity/expo-mobile-sdk";

...
   // In your sendAPIRequest function

    const headers = HumanSecurity.BD.headersForURLRequest(appId);

    // Add this for account defender
    HumanSecurity.AD.registerOutgoingUrlRequest(url, appId);

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

...

Here’s what the code includes:

Call the HumanSecurity.AD.registerOutgoingUrlRequest(url: string, appId?: string): void function before sending the URL request. If you're using a custom HttpClient service, add this function to the request interceptor.

Set additional data (optional)

You can set additional data to configure HUMAN's backend with extra parameters.

To do so, call HumanSecurity.AD.setAdditionalData(parameters: { [key: string]: string }, appId?: string): void after calling HumanSecurity.startWithAppId(appId: string, policy?: HSPolicy): Promise<void>.

For example:

import { HumanSecurity } from "@humansecurity/expo-mobile-sdk";

function setAdditionalDataForAccountDefender() {
    try {
        const additionalData = {
          my_key1: "hello",
          my_key2: "world"
      };
      HumanSecurity.AD.setAdditionalData(additionalData, "<APP_ID>");
    } catch (error) {
        console.error(`Failed to Set Additional Data: ${error.message}`);
    }
}

Simulate a challenge

You can simulate a challenge to test the integration. To do so, follow the steps in our help article.