All Products
Search
Document Center

Web Application Firewall:Integrate the Android SDK

Last Updated:Mar 31, 2026

To use anti-crawler rules for your Android apps, integrate the app protection software development kit (SDK) first. This topic walks you through the required steps.

Background

The app protection SDK signs every request your app sends. WAF verifies these signatures to distinguish legitimate traffic from bots and other malicious actors, protecting your app's backend services.

Limitations

  • Supported Android ABI architectures: arm64-v8a and armeabi-v7a

  • Minimum Android API level: 16

  • After calling init, wait at least 2 seconds before calling vmpSign. The SDK needs this time to fully initialize its security capabilities. This is a recommended delay and can be adjusted as needed. A shorter delay may reduce protection effectiveness.

Prerequisites

Before you begin, ensure that you have:

  • The SDK package for Android apps (tigertally-X.Y.Z-xxxxxx-android.tgz), which includes AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar. To get the SDK, submit a ticket.

  • The SDK authentication key (appkey). After you enable Bot Management, go to Bot Management > App Protection and click Obtain and Copy AppKey.

image
Each Alibaba Cloud account has one unique appkey, which applies to all WAF-protected domains and works across Android, iOS, and HarmonyOS app integrations. Sample appkey format: **OpKLvM6zliu6KopyHIhmneb_u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK**

Step 1: Create a project

Open Android Studio and create an Android project using the configuration wizard. The project directory looks like the following:

image.png

Step 2: Integrate the AAR package

  1. Extract tigertally-X.Y.Z-xxxxxx-android.tgz and copy all AAR files from the resulting folder into the libs directory of your main module. The exact path may vary depending on your project configuration.

    image.png

  2. Open build.gradle for your app module. Add the libs directory as a local dependency source and declare compilation dependencies for both AAR files and their required third-party libraries:

    Important

    Replace X.Y.Z in the AAR file names with the actual version number from the SDK package.

    dependencies {
        // ...
        implementation files('libs/AliTigerTally_X.Y.Z.aar')
        implementation files('libs/AliCaptcha_X.Y.Z.aar')
    
        // Required third-party libraries
        implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
        implementation 'com.squareup.okhttp3:okhttp:3.11.0'
        implementation 'com.squareup.okio:okio:1.14.0'
    }
  3. If you use ProGuard for code obfuscation, add the following -keep rules to your ProGuard configuration file to preserve the SDK's API methods:

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

Step 3: Filter SO CPU architectures

If your project does not already include SO files, add the following ABI filter to your build.gradle file:

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

Step 4: Declare app permissions

Add the following permissions to your AndroidManifest.xml.

Required:

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

Optional — grant these to improve device signal collection accuracy:

PermissionGrant typeDescription
android.permission.BLUETOOTHInstall-timeCollects Bluetooth state for device fingerprinting.
android.permission.READ_PHONE_STATEInstall-timeReads device identifiers such as IMEI.
android.permission.ACCESS_WIFI_STATEInstall-timeReads Wi-Fi connection details (SSID, BSSID).
android.permission.ACCESS_NETWORK_STATEInstall-timeReads network connectivity state.
android.permission.READ_EXTERNAL_STORAGERuntime (Android 6.0+)Reads device storage for signal collection.
android.permission.WRITE_EXTERNAL_STORAGERuntime (Android 6.0+)Writes to device storage for signal collection.

Install-time permissions are automatically granted at install. Runtime permissions on Android 6.0 and later must be requested dynamically at runtime; they are not automatically granted at install time.

Step 5: Add the integration code

Add imports

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

Set data signing

Complete the following steps in order: set a user account identifier, initialize the SDK, and then sign requests.

1. Set the user account

Assign a custom end-user identifier so you can apply targeted WAF policies per user.

public static int setAccount(String account)
ParameterTypeRequiredDescription
accountStringNoA string that identifies the user. Use a desensitized format.

Return value: 0 on success, -1 on failure.

Sample code:

// For guest users, skip setAccount and initialize directly.
// After the user logs in, call setAccount and re-initialize.
String account = "user001";
TigerTallyAPI.setAccount(account);

2. Initialize the SDK

Call init once to collect device information. You can call it again later to start a new collection cycle for different business contexts.

public static int init(Context context, String appkey, int collectType,
                       Map<String, String> otherOptions, TTInitListener listener);

Parameters:

ParameterTypeRequiredDefaultDescription
contextContextYesYour application context.
appkeyStringYesThe SDK authentication key.
collectTypeintYesThe data collection mode. See the table below.
otherOptionsMap\<String, String\>NonullAdditional options.
listenerTTInitListenerNonullCallback for initialization result.

`collectType` values:

ValueDescription
TT_DEFAULTCollects all data.
TT_NO_BASIC_DATAExcludes basic device data: device name, Android version, and screen resolution.
TT_NO_IDENTIFY_DATAExcludes device identifiers: IMEI, IMSI, SimSerial, BuildSerial (SN), and MAC address.
TT_NO_UNIQUE_DATAExcludes unique identifiers: Open Anonymous Device Identifier (OAID), Google Advertising ID, and Android ID.
TT_NO_EXTRA_DATAExcludes extended device data: malicious/gray-area app list, LAN IP, DNS IP, Wi-Fi details (SSID, BSSID), nearby Wi-Fi list, location, and sensor data.
TT_NOT_GRANTEDExcludes all of the above privacy data.

Combine multiple exclusion flags with |. For example, TT_NO_BASIC_DATA | TT_NO_UNIQUE_DATA excludes both categories. Select a mode that satisfies your compliance requirements while preserving enough data for effective bot detection.

`otherOptions` values:

KeyDescriptionDefaultRequired
IPv60: Use IPv4. 1: Use IPv6.0No
Intl0: Report to the Chinese mainland. 1: Report outside the Chinese mainland. Set to 1 for WAF instances outside the Chinese mainland; otherwise, use the default of 0.0No
CustomUrlDomain name of the data reporting server. Example: https://cloudauth-device.us-west-1.aliyuncs.comNo
CustomHostHost of the data reporting server. Example: cloudauth-device.us-west-1.aliyuncs.comNo
For most international regions, setting Intl is sufficient. Use CustomUrl and CustomHost only when reporting to a specific endpoint, such as the US (West) site.

Initialization callback:

public interface TTInitListener {
    // code: the status code of the initialization call
    void onInitFinish(int code);
}

Callback status codes (`TTCode`):

CodeValueDescription
TT_SUCCESS0SDK initialized successfully.
TT_NOT_INIT-1SDK is not initialized.
TT_NOT_PERMISSION-2Required Android permissions are not fully granted.
TT_UNKNOWN_ERROR-3Unknown system error.
TT_NETWORK_ERROR-4Network error.
TT_NETWORK_ERROR_EMPTY-5Network error — response body is empty.
TT_NETWORK_ERROR_INVALID-6Network error — response format is invalid.
TT_PARSE_SRV_CFG_ERROR-7Failed to parse server configuration.
TT_NETWORK_RET_CODE_ERROR-8Gateway did not return a value.
TT_APPKEY_EMPTY-9Appkey is empty.
TT_PARAMS_ERROR-10Parameter error.
TT_FGKEY_ERROR-11Key calculation error.
TT_APPKEY_ERROR-12SDK version does not match the appkey version.

Return value: 0 on success, -1 on failure.

Sample code:

// Replace with your actual appkey.
final String appkey = "******";

Map<String, String> options = new HashMap<>();
options.put("IPv6", "0");   // Use IPv4
options.put("Intl", "1");   // Report outside the Chinese mainland

// To report to the US (West) site specifically:
// options.put("CustomUrl", "https://cloudauth-device.us-west-1.aliyuncs.com");
// options.put("CustomHost", "cloudauth-device.us-west-1.aliyuncs.com");

// Full data collection
int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_DEFAULT, options, null);

// Selective privacy exclusion — combine flags with |
int privacyFlag = TigerTallyAPI.TT_NO_BASIC_DATA | TigerTallyAPI.TT_NO_UNIQUE_DATA;
int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, privacyFlag, options, null);

// Exclude all privacy data
int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_NOT_GRANTED, options, null);

Log.d("AliSDK", "ret:" + ret);

3. Hash data (custom signing only)

Skip this step if you are using default signing.

For custom signing, call vmpHash to compute a hash of the request data. Pass the request body for POST, PUT, and PATCH requests, or the full URL for GET and DELETE requests. Then add the returned whash string to the ali_sign_whash HTTP request header.

public enum RequestType { GET, POST, PUT, PATCH, DELETE }

public static String vmpHash(RequestType type, byte[] input);
ParameterTypeRequiredDescription
typeRequestTypeYesThe HTTP request method.
inputbyte[]YesThe data to hash. Cannot be an empty string. For GET/DELETE, use the full URL (including path and query parameters).

Return value: A whash 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);

4. Sign data

Call vmpSign to generate a wtoken string for request authentication. Add the wtoken to the wToken HTTP request header.

  • Default signing: pass the request body as input.

  • Custom signing: pass the whash from vmpHash as input.

public static String vmpSign(int type, byte[] input);
ParameterTypeRequiredDescription
typeintYesThe signature type. Must be 1.
inputbyte[]YesThe data to sign. For default signing: the request body. For custom signing: the whash string. If the request body is empty, pass null or "".getBytes("UTF-8").

Return value: A wtoken string.

Sample code:

// Default signing
String body = "i am the request body, encrypted or not!";
String wtoken = TigerTallyAPI.vmpSign(1, body.getBytes("UTF-8"));
Log.d("AliSDK", "wToken:" + wtoken);

// 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);

// Custom signing — 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
  • For custom signing, when you configure scenario-specific anti-bot policies in the console, set Custom Signature Field to ali_sign_whash.

  • When computing the whash for a GET request, the input URL must exactly match the final URL used in the network request. Some frameworks automatically URL-encode Chinese characters or parameters — account for this when passing the URL.

  • vmpHash does not accept empty strings. The input URL must include a path or query parameter.

  • If vmpHash or vmpSign returns one of the following strings, an initialization error occurred:

  • "you must call init first"init was not called before signing.

  • "you must input correct data" — the input data is invalid.

  • "you must input correct type" — the input type is invalid.

Perform two-factor authentication

If WAF determines that a request is suspicious, it can trigger a CAPTCHA challenge to verify the user. Use the following methods to handle this flow.

1. Check whether a challenge is required

After receiving a response from your server, inspect the cookie and body fields to determine if WAF has flagged the request. Merge all Set-Cookie entries from the response headers into a single cookie string before calling this method.

public static int cptCheck(String cookie, String body)
ParameterTypeRequiredDescription
cookieStringYesAll cookies from the response headers, merged into a single string.
bodyStringYesThe full response body.

Return value: 0 — request passed; 1 — CAPTCHA challenge required.

Sample code:

String cookie = "key1=value1;key2=value2;";
String body = "....";
int recheck = TigerTallyAPI.cptCheck(cookie, body);
Log.d("AliSDK", "recheck:" + recheck);

2. Show the CAPTCHA slider

If cptCheck returns 1, create and display a CAPTCHA slider to the user.

public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener);
ParameterTypeRequiredDescription
activityActivityYesThe current activity.
optionTTOptionYesSlider configuration.
listenerTTListenerYesCallback for verification result.

Return value: A TTCaptcha object with show(), dismiss(), and getTraceId() methods.

`TTOption` fields:

FieldTypeDefaultDescription
cancelablebooleanWhether tapping outside the slider dismisses it.
customUriStringA custom CAPTCHA page (local HTML file or remote URL).
languageStringThe language for the slider UI (for example, "cn").

`TTListener` callbacks:

public interface TTListener {
    void success(TTCaptcha captcha, String data);  // data = certifyId (token)
    void failed(TTCaptcha captcha, String code);   // code = error code
}

Slider error codes:

CodeDescription
1001Verification failed.
1002System exception.
1003Parameter error.
1005Verification canceled by user.
8001Slider invocation error.
8002Abnormal slider verification data.
8003Internal slider verification exception.
8004Network error.
The failed callback fires when an exception is detected after the user completes the sliding gesture, not necessarily when the user abandons the challenge.

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();

Best practice example

The following end-to-end example shows the complete integration flow: initialization, request signing, sending the request, and handling the CAPTCHA challenge.

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(() -> {
            // Step 1: Initialize the SDK.
            Map<String, String> options = new HashMap<>();
            // options.put("Intl", "1"); // Uncomment for international reporting.
            int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
            // int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_NOT_GRANTED, null, null); // No privacy data
            Log.d(TAG, "tiger tally init: " + ret);

            // Step 2: Wait at least 2 seconds before signing — initialization is asynchronous.
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // Step 3: Sign the request data.
            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);

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

            // Step 4: Send the request and check whether a CAPTCHA is required.
            doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
                int recheck = TigerTallyAPI.cptCheck(cookie, body);
                Log.d(TAG, "captcha check result: " + recheck);

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

    // Show the CAPTCHA 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 a POST request with the wToken and optional whash headers.
    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)  // Required: WAF signature token
                    .addHeader("Host",   host)
                    .post(RequestBody.create(MediaType.parse("text/x-markdown"), body.getBytes()));

            if (whash != null) {
                builder.addHeader("ali_sign_whash", whash);  // Required for custom signing
            }
            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);
    }
}

After the SDK is integrated, it automatically:

  1. Signs every outgoing request with a wToken header for WAF to verify.

  2. Optionally attaches an ali_sign_whash header for custom signing scenarios.

  3. Enables your app to detect and respond to WAF CAPTCHA challenges.