All Products
Search
Document Center

Web Application Firewall:Integrate the SDK for Android apps

Last Updated:Aug 27, 2025

To configure anti-crawler rules for your Android apps, you must first integrate the app protection SDK. This topic provides the required integration steps.

Background information

The app protection SDK ensures trusted communication by signing all requests initiated from your app. The Web Application Firewall (WAF) server then verifies these signatures. This process lets WAF identify and block malicious requests from bots and other attackers, securing your application's services.

Limits

  • The SDK supports the arm64-v8a and armeabi-v7a architectures for Android OS.

  • The API level for Android OS must be 16 or higher.

  • The init method performs time-consuming operations. To ensure security features are fully initialized, wait at least 2 seconds after calling init before calling the vmpSign method. This is a recommended delay to maximize protection and can be adjusted as needed. However, shorter delays may prevent the security capabilities from taking full effect.

  • When using ProGuard for code obfuscation, use the -keep option to preserve the SDK's API methods. For example:

-keep class com.aliyun.TigerTally.** {*;}
-keep class com.aliyun.captcha.* {*;}
-keepclassmembers,allowobfuscation class * {
     @com.alibaba.fastjson.annotation.JSONField <fields>;
}
-keep class com.alibaba.fastjson.** {*;}

Prerequisites

  • You have obtained the SDK for your Android apps.

    To get the SDK, submit a ticket.

    The SDK for Android apps includes two AAR files: AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar, where X.Y.Z represents the version number.

  • Obtain the SDK authentication key (appkey).

    After you enable Bot Management, navigate to the Bot Management > App Protection page and click Obtain and Copy AppKey to obtain the appkey. This key serves as a credential and is required to initialize the SDK in your application code.

    image

    Note

    Each Alibaba Cloud account is assigned a single, unique appkey that applies to all domains protected by WAF. This key is used for SDK integration across Android, iOS, and HarmonyOS apps.

    Sample appkey: ****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.

Step 1: Create a project

Use Android Studio as an example.

Create an Android project by following the configuration wizard. The project directory is shown in the following figure.

image.png

Step 2: Integrate the AAR package

  1. Extract the tigertally-X.Y.Z-xxxxxx-android.tgz package. Copy all the AAR files from the resulting folder into the libs directory of your main module (note that the specific path may vary based on your project's configuration).image.png

  2. Open the build.gradle file of your app. Add the libs directory as a dependency source and add compilation dependencies for AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar.

    Important

    You must replace the version number X.Y.Z in the AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar file names with the actual one.

    The configuration is shown as follows:

    dependencies {
        // ...
        implementation files('libs/AliTigerTally_X.Y.Z.aar')
        implementation files('libs/AliCaptcha_X.Y.Z.aar')
      
        // Third-party library dependencies
        implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
        implementation 'com.squareup.okhttp3:okhttp:3.11.0'
        implementation 'com.squareup.okio:okio:1.14.0
    }

Step 3: Filter the SO CPU architecture

If your project has not used SO files before, add the following configuration in your build.gradle file.

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'armeabi-v7a'
        }
    }
}

Step 4: Request permissions for the apps

  • Required permissions

    <uses-permission android:name="android.permission.INTERNET"/>
  • Optional permissions

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Note

For Android 6.0 and later, you must dynamically request the android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE permissions.

Step 5: Add the integration code

Add header files

import com.alibaba.fastjson.*;
import com.aliyun.tigertally.*;

Set data signing

  1. Set a custom end user ID for your business.

    This lets you flexibly configure WAF mitigation policies.

    /**
    * Set the user account
    *
    * @param account The account
    * @return The error code
    */
    public static int setAccount(String account)
    • Parameters:

      account: A string that identifies a user. We recommend using a desensitized format.

    • Return value: Returns 0 if the setting is successful, or -1 if it fails. Data type: int.

    • Sample code:

      // For a guest, you can skip setAccount and directly initialize the SDK. After logon, call setAccount and re-initialize.
      String account = "user001";
      TigerTallyAPI.setAccount(account);
  2. Initialize the SDK and perform data collection.

    An initialization process collects device information once. You can call the init method again to start a new collection process based on different business requirements. There are three modes for initialization: full collection, custom privacy collection, and non-privacy collection. The non-privacy mode does not collect fields involving user privacy, such as imei, imsi, simSerial, wifiMac, wifiList, bluetoothMac, and androidId.

    Note

    We recommend that you select a collection mode that complies with your internal compliance requirements and ensures data integrity. Complete data helps identify potential risks more effectively.

    // Initialization callback
    public interface TTInitListener {
        // code indicates the status code of the interface call
        void onInitFinish(int code);
    }
    
    /**
     * SDK initialization with callback
     *
     * @param appkey The appkey
     * @param type The type of data to collect
     * @param otherOptions Various parameter options
     * @return The error code
     */
    public static int init(Context context, String appkey, int collectType,
                           Map<String, String> otherOptions, TTInitListener listener);
    • Parameters:

      • context: The context of your application. Data type: Context.

      • appkey: Your SDK appkey. Data type: String.

      • collectType: The data collection mode. Data type: int. Valid values:

        Field name

        Description

        Example

        TT_DEFAULT

        Collects all data.

        TigerTallyAPI.TT_DEFAULT

        TT_NO_BASIC_DATA

        Does not collect basic device data. Includes: device name (Build.DEVICE), Android version number (Build.VERSION#RELEASE), and screen resolution.

        TigerTallyAPI.X | TigerTallyAPI.Y

        (Indicates that neither X nor Y is collected. X and Y represent the field names of specific items.)

        TT_NO_IDENTIFY_DATA

        Does not collect device identifier data. Includes: IMEI, IMSI, SimSerial, BuildSerial (SN), and MAC address.

        TT_NO_UNIQUE_DATA

        Does not collect unique identifier data. Includes: OAID, Google Advertising ID, and Android ID.

        TT_NO_EXTRA_DATA

        Does not collect extended device data. Includes: malicious/gray-area app list, LAN IP, DNS IP, connected Wi-Fi information (SSID, BSSID), nearby Wi-Fi list, location information, and sensor information.

        TT_NOT_GRANTED

        Does not collect any of the privacy data listed above.

        TigerTallyAPI.TT_NOT_GRANTED

      • otherOptions: Optional. Data type: Map<String,String>. Default value: null. Valid values:

        Field name

        Description

        Example

        IPv6

        Specifies whether to use an IPv6 domain name to report device information. Valid values:

        • 0 (default): Use an IPv4 domain name.

        • 1: Use an IPv6 domain name.

        1

        Intl

        Specifies the region where device information is reported. Valid values:

        • 0 (default): the Chinese mainland

        • 1: outside the Chinese mainland

        Set this to 1 for WAF instances outside the Chinese mainland; otherwise, use the default of 0.

        1

        CustomUrl

        Sets the domain name of the data reporting server.

        https://cloudauth-device.us-west-1.aliyuncs.com

        CustomHost

        Sets the host of the data reporting server.

        cloudauth-device.us-west-1.aliyuncs.com

        Note

        For common international sites, setting the Intl parameter is sufficient. The CustomUrl and CustomHost parameters are only required when reporting to a specific site, such as the US (West) site: https://cloudauth-device.us-west-1.aliyuncs.com.

      • listener: The SDK initialization callback interface. Data type: TTInitListener. You can check the specific status of the initialization result in the callback. Default value: null.

        TTCode

        Code

        Remarks

        TT_SUCCESS

        0

        The SDK is initialized.

        TT_NOT_INIT

        -1

        The SDK is not initialized.

        TT_NOT_PERMISSION

        -2

        The required basic Android permissions are not fully granted to the SDK.

        TT_UNKNOWN_ERROR

        -3

        An unknown system error occurred.

        TT_NETWORK_ERROR

        -4

        A network error occurred.

        TT_NETWORK_ERROR_EMPTY

        -5

        A network error occurred. The returned content is an empty string.

        TT_NETWORK_ERROR_INVALID

        -6

        The format of the returned value is invalid.

        TT_PARSE_SRV_CFG_ERROR

        -7

        Failed to parse the server configuration.

        TT_NETWORK_RET_CODE_ERROR

        -8

        The gateway failed to return a value.

        TT_APPKEY_EMPTY

        -9

        The appkey is empty.

        TT_PARAMS_ERROR

        -10

        Other parameter errors occurred.

        TT_FGKEY_ERROR

        -11

        A key calculation error occurred.

        TT_APPKEY_ERROR

        -12

        The SDK version does not match the appkey version.

    • Return value: Returns 0 if the initialization is successful, or -1 if it fails. Data type: int.

    • Sample code:

      // Appkey represents the authentication key assigned by the Alibaba Cloud customer platform.
      final String appkey="******";
      // Optional parameters. You can configure IPv6 and international reporting.
      Map<String, String> options = new HashMap<>();
      options.put("IPv6", "0");   // Configure as IPv4.
      //options.put("Intl", "0");   // Report to the Chinese mainland.
      options.put("Intl", "1"); // Report to regions outside the Chinese mainland.
      
      // Report to the US (West) site.
      //options.put("CustomUrl", "https://cloudauth-device.us-west-1.aliyuncs.com"); 
      //options.put("CustomHost", "cloudauth-device.us-west-1.aliyuncs.com"); 
      
      // An initialization collection indicates that the device information is collected once. You can call the init function again to re-initialize the collection based on different business requirements.
      // Full data collection.
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_DEFAULT, options, null);
      
      // Specify privacy data collection. Different privacy data can be combined using "|".
      int privacyFlag = TigerTallyAPI.TT_NO_BASIC_DATA | TigerTallyAPI.TT_NO_UNIQUE_DATA;
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, privacyFlag, options, null);
      
      // Do not collect privacy fields.
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_NOT_GRANTED, options, null);
      Log.d("AliSDK", "ret:" + ret);
  3. Hash data.

    This custom signing interface calculates a hash of the input data and returns the generated whash string as the custom signature. The request body must be passed as input for POST, PUT, and PATCH requests, while the complete URL must be used as input for GET and DELETE requests. Additionally, the whash string needs to be added to the ali_sign_whash field in the HTTP request header.

    // Request type:
    public enum RequestType { GET, POST, PUT, PATCH, DELETE }
    
    /**
     *  Custom hash signature data 
     *
     * @param type Data type
     * @param input Hash data
     * @return whash
     */
    public static String vmpHash(RequestType type, byte[] input);
  • Parameters:

    • type: The data type. Data type: RequestType. Valid values:

      • GET: A GET request.

      • POST: A POST request.

      • PUT: A PUT request.

      • PATCH: A PATCH request.

      • DELETE: A DELETE request.

    • input: The data to be signed. Data type: byte[].

  • Return value: A whash string. Data type: String.

  • Sample code:

    // GET request
    String url = "https://tigertally.aliyun.com/apptest";
    String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
    Log.d("AliSDK", "whash:" + whash);
    
    // POST request
    String body = "hello world";
    String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
    Log.d("AliSDK", "whash:" + whash);

    This interface call is not required when using default signing. For custom signing, the interface must be called for hash verification before the data is signed.

  1. Sign data.

    This method uses vmp technology to sign the input data and returns a wtoken string for request authentication.

    /**
     * Data signing
     *
     * @param type Signature type
     * @param input Signature data
     * @return wtoken
     */
    public static String vmpSign(int type, byte[] input);
  • Parameters:

    • type: The data signature type. The value must be 1. Data type: int.

    • input: The data to be signed. This is typically the entire requestbody or the whash for custom signing. Data type: byte[].

  • Return value: A wtoken string. Data type: String.

  • Sample code:

    // The console is configured with default signing, which means custom signing is not selected.
    String body = "i am the request body, encrypted or not!";
    String wtoken = TigerTallyAPI.vmpSign(1, body.getBytes("UTF-8"));
    Log.d("AliSDK", "wToken:" + wtoken);
    
    // The console is configured with custom signing.
    // GET request.
    String url = "https://tigertally.aliyun.com/apptest";
    String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
    String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
    Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
    
    // POST request.
    String body = "hello world";
    String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
    String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
    Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
    Important
    • When you call vmpHash for custom signing, the input parameter of the vmpSign method is the generated whash string. Furthermore, when you configure scenario-specific anti-bot policies for the app, the value of the Custom Signature Field must be set to ali_sign_whash.

    • When you call vmpHash to generate the whash for a GET request, ensure that the input URL is identical to the final URL used in the network request. Pay special attention to URL encoding. Some frameworks automatically perform URL encoding on Chinese characters or parameters.

    • The input parameter of the vmpHash method does not support empty strings. If the input is a URL, a path or parameter must exist.

    • When you call vmpSign, if the request body is empty (for example, the body of a POST or GET request is empty), pass a null object or the byte value of an empty string, such as "".getBytes("UTF-8").

    • If the whash or wtoken is one of the following strings, an exception occurred during initialization:

      • you must call init first: Indicates that the init method was not called.

      • you must input correct data: Indicates that the input data is incorrect.

      • you must input correct type: Indicates that the input type is incorrect.

Perform two-factor authentication

  1. Evaluate the result.

    Check the cookie and body fields in the response to determine if a secondary verification is required. The header may contain multiple Set-Cookie entries, which must be merged into the cookie format before calling this interface.

    /**
     * Evaluate whether to perform the two-factor authentication
     *
     * @param cookie Cookie
     * @param body Body
     * @return 0: Pass 1: Two-factor authentication
     */
    public static int cptCheck(String cookie, String body)
    • Parameters:

      • cookie: All cookies in the request response. Data type: String.

      • body: The entire body in the request response. Data type: String.

    • Return value: Returns the decision result. 0 indicates that the request passed. 1 indicates that two-factor authentication is required. Data type: int.

    • Sample code:

      String cookie = "key1=value1;kye2=value2;";
      String body = "....";
      int recheck = TigerTallyAPI.cptCheck(cookie, body);
      Log.d("AliSDK", "recheck:" + recheck);
  2. Create a slider.

    Create a slider object based on the result returned by cptCheck. The TTCaptcha object provides the show and dismiss methods, which show and hide the slider window respectively. TTOption encapsulates the configurable parameters for the slider. TTListener contains two callback states for the slider. If you require a custom slider window, pass the address of the custom page. Both local HTML files and remote pages are supported.

    /**
     * Create a slider object
     *
     * @param activity The current page activity
     * @param option The parameters
     * @param listener The callback
     * @return The slider verification object
     */
    public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener);
    
    
    /**
     * The slider object
     */
    public class TTCaptcha {
        /**
         * Show the slider
         */
        public void show();
    
        /**
         * Hide the slider
         */
        public void dismiss();
    
        /**
         * Obtain the slider traceId for data statistics
         */
        public String getTraceId();
    }
    
    /**
     * The slider parameters
     */
    public static class TTOption {
        // Specifies whether to hide the slider by clicking a blank area.
        public boolean cancelable;
    
        // The custom page. Local HTML files and remote URLs are supported.
        public String customUri;
    
        // Set the language
        public String language;
    }
    
    /**
     * The slider callback
     */
    public interface TTListener {
        /**
         * Verification successful
         *
         * @param captcha The slider object
         * @param data token, defaults to certifyId
         */
        void success(TTCaptcha captcha, String data);
    
        /**
         * Verification failed
         *
         * @param captcha The slider object
         * @param code The error code
         */
        void failed(TTCaptcha captcha, String code);
    }
    • Parameters:

      • activity: The current page activity. Data type: Activity.

      • option: The slider configuration parameters. Data type: TTOption.

      • listener: The slider status callback. Data type: TTlistener.

    • Return value: A slider object. Data type: TTCaptcha.

    • Sample code:

    TTCaptcha.TTOption option = new TTCaptcha.TTOption();
    // option.customUri  = "file:///android_asset/ali-tt-captcha-demo.html";
    option.language   = "cn";
    option.cancelable = false;
    
    TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
        @Override
        public void success(TTCaptcha captcha, String data) {
            Log.d(TAG, "captcha check success:" + data);
        }
        @Override
        public void failed(TTCaptcha captcha, String code) {
            Log.d(TAG, "captcha check failed:" + code);
        }
    });
    captcha.show();
    Note

    Authentication failed indicates that an exception was detected after the user finished sliding.

    The following list describes the error codes:

    • 1001: The verification failed.

    • 1002: System exception.

    • 1003: Parameter error.

    • 1005: Verification canceled.

    • 8001: Slider invocation error.

    • 8002: Abnormal slider verification data.

    • 8003: Internal slider verification exception.

    • 8004: Network error.

Best practice example

package com.aliyun.tigertally.apk;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.aliyun.TigerTally.TigerTallyAPI;
import com.aliyun.TigerTally.captcha.api.TTCaptcha;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class DemoActivity extends AppCompatActivity {
    private final static String TAG = "TigerTally-Demo";

    private final static String APP_HOST = "******";
    private final static String APP_URL  = "******";
    private final static String APP_KEY  = "******";

    private final static OkHttpClient okHttpClient = new OkHttpClient();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);

        doTest();
    }

    private void doTest() {
        Log.d(TAG, "captcha flow");
        new Thread(() -> {
            // Initialization.
            Map<String, String> options = new HashMap<>();
            //options.put("Intl", "1"); // Configure for international reporting.
            // Full data collection.
            int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
            // Do not collect privacy fields.
            // int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_NOT_GRANTED, null, null);
            Log.d(TAG, "tiger tally init: " + ret);

            // Do not call synchronously immediately.
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            //  Sign.
            String data = "hello world";
            String whash = null, wtoken = null;
            // Custom signing.
            whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
            wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
            Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);

            // Normal signing.
            // wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
            // Log.d(TAG, "tiger tally vmp: " + wtoken);


            // Request interface
            doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
                // Determine whether to display the slider
                int recheck = TigerTallyAPI.cptCheck(cookie, body);
                Log.d(TAG, "captcha check result: " + recheck);

                if (recheck == 0) return;
                this.runOnUiThread(this::doShow);
            });
        }).start();
    }

    // Display the slider.
    public void doShow() {
        Log.d(TAG, "captcha show");

        TTCaptcha.TTOption option = new TTCaptcha.TTOption();
        // option.customUri = "file:///android_asset/ali-tt-captcha-demo.html";
        option.language   = "cn";
        option.cancelable = false;

        TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
            @Override
            public void success(TTCaptcha captcha, String data) {
                Log.d(TAG, "captcha check success:" + data);
            }

            @Override
            public void failed(TTCaptcha captcha, String code) {
                Log.d(TAG, "captcha check failed:" + code);
            }
        });

        captcha.show();
    }

    // Send the request.
    public static void doPost(String url, String host, String whash, String wtoken, String body, Callback callback) {
        Log.d(TAG, "start request post");

        int responseCode = 0;
        String responseBody = "";
        StringBuilder responseCookie = new StringBuilder();
        try {
            Request.Builder builder = new Request.Builder()
                    .url(url)
                    .addHeader("wToken", wtoken)
                    .addHeader("Host",   host)
                    .post(RequestBody.create(MediaType.parse("text/x-markdown"), body.getBytes()));

            if (whash != null) {
                builder.addHeader("ali_sign_whash", whash);
            }
            Response response = okHttpClient.newCall(builder.build()).execute();

            responseCode = response.code();
            responseBody = response.body() == null ? "" : response.body().string();
            for (String item : response.headers("Set-Cookie")) {
                responseCookie.append(item).append(";");
            }

            Log.d(TAG, "response code:" + responseCode);
            Log.d(TAG, "response cookie:" + responseCookie);
            Log.d(TAG, "response body:" + (responseBody.length() > 100 ? responseBody.substring(0, 100) : ""));

            if (response.isSuccessful()) {
                Log.d(TAG, "success: " + response.code() + ", " + response.message());
            } else {
                Log.e(TAG, "failed: " + response.code() + ", " + response.message());
            }

            response.close();
        } catch (Exception e) {
            e.printStackTrace();
            responseCode = -1;
            responseBody = e.toString();
        } finally {
            if (callback != null) {
                callback.onResponse(responseCode, responseCookie.toString(), responseBody);
            }
        }
    }

    public interface Callback {
        void onResponse(int code, String cookie, String body);
    }
}