To configure anti-crawler scenario-based rules for bot management in the console, you must integrate the WAF App Protection SDK. This topic explains how to integrate the SDK with Android apps.
Background
The App Protection SDK signs requests from app clients. The Web Application Firewall (WAF) server verifies these request signatures to identify risks, block malicious requests, and protect the app.
Limitations
-
For Android, the SDK supports the arm64-v8a and armeabi-v7a architectures.
-
The Android API level must be 16 or higher.
-
The init method can be time-consuming. To ensure full protection, wait at least 2 seconds between calling the init method and the vmpSign method. This delay is recommended for optimal protection but is not mandatory. You can adjust this delay based on your business needs, but a shorter delay might prevent the security features from being fully effective.
-
When using ProGuard for code obfuscation, use the -keep option to preserve the SDK's 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
-
Get the SDK for your Android app.
To get the SDK, submit a ticket to our product technical experts.
NoteThe Android SDK includes two AAR files: AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar, where X.Y.Z is the version number.
You have obtained the SDK authentication key (also known as the appkey).
After you enable Bot Management, navigate to the page. In the app list, click Obtain and Copy AppKey to get the SDK authentication key. This key is required for SDK initialization and must be included in your integration code.
NoteEach Alibaba Cloud account has a unique appkey for all domain names protected by WAF. This appkey is used for SDK integration across Android, iOS, and HarmonyOS apps.
Example authentication key:
****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.
Step 1: Create a project
In Android Studio, follow the configuration wizard to create a new Android project. The project directory is shown in the figure below.

Step 2: Integrate AAR
-
Extract the tigertally-X.Y.Z-xxxxxx-android.tgz SDK file, and copy all AAR files from the extracted folder to your main module's libs directory (the exact path depends on your project configuration).

-
Open your app's build.gradle file and add dependencies on AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar from the
libsdirectory.ImportantReplace X.Y.Z in the AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar filenames with your AAR files' version number.
The configuration is 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 SO CPU architectures
If your project hasn't used SO files, add the following configuration to your build.gradle file.
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
}
Step 4: Request permissions
-
Required permission
<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"/>
For Android 6.0 and later, you must dynamically request android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE.
Step 5: Add integration code
1. Add header files
import com.alibaba.fastjson.*;
import com.aliyun.tigertally.*;
2. Set up data signing
-
Set a custom ID for each end user. This allows you to configure WAF mitigation policies more flexibly.
/** * Sets the user account. * * @param account The user account. * @return An error code. */ public static int setAccount(String account)-
Parameters:
-
account: String. A string that identifies a user. We recommend using a desensitized format.
-
-
Return value: int. Returns 0 on success, or -1 on failure.
-
Sample code:
// For a guest, you can skip setAccount and directly initialize the SDK. After the user logs in, call setAccount and reinitialize. String account = "user001"; TigerTallyAPI.setAccount(account);
-
-
Initialize the SDK and perform an initial data collection.
An initial data collection gathers device information once. You can call the
initfunction again to perform a new collection based on your business requirements. Three collection modes are available: full collection, custom privacy collection, and non-privacy collection. The non-privacy mode does not collect data from fields related to end user privacy, including: imei, imsi, simSerial, wifiMac, wifiList, bluetoothMac, and androidId.NoteSelect a collection mode that aligns with your compliance requirements and ensures data integrity. Complete data improves risk identification.
// Initialization callback public interface TTInitListener { // The code parameter indicates the status code of the interface call. void onInitFinish(int code); } /** * Initializes the SDK with a callback. * * @param context The application context. * @param appkey The SDK appkey. * @param collectType The data collection mode. * @param otherOptions Optional parameters. * @param listener The initialization callback. * @return An error code. */ public static int init(Context context, String appkey, int collectType, Map<String, String> otherOptions, TTInitListener listener);-
Parameters:
-
context: Context. Your application's context.
-
appkey: String. Your SDK appkey.
-
collectType: int. The collection mode. Valid values:
Parameter
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 (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 preceding privacy data.
TigerTallyAPI.TT_NOT_GRANTED
-
otherOptions: Map<String, String>. Optional data collection parameters. This can be null. Available parameters:
Parameter
Description
Example
IPv6
Specifies whether to use an IPv6 domain name to report device information.
-
0 (default): Use an IPv4 domain name.
-
1: Use an IPv6 domain name.
1
Intl
Specifies whether to report device information to a region outside the Chinese mainland.
-
0 (default): Report to the Chinese mainland.
-
1: Report to a region outside the Chinese mainland.
1
CustomUrl
The domain name of the data reporting server.
https://cloudauth-device.us-west-1.aliyuncs.com
CustomHost
The host of the data reporting server.
cloudauth-device.us-west-1.aliyuncs.com
NoteFor most international sites, set only the Intl parameter. To report data to a specific site, set the CustomUrl and CustomHost parameters. Available sites include:
-
If
Intlis set to0, data is reported to the default site in China (Shanghai): https://cloudauth-device.cn-shanghai.aliyuncs.com -
If
Intlis set to1:-
The default site is Singapore: https://cloudauth-device.ap-southeast-1.aliyuncs.com
-
Indonesia (Jakarta): https://cloudauth-device.ap-southeast-5.aliyuncs.com
-
US (Silicon Valley): https://cloudauth-device.us-west-1.aliyuncs.com
-
Germany (Frankfurt): https://cloudauth-device.eu-central-1.aliyuncs.com
-
China (Hong Kong): https://cloudauth-device.cn-hongkong.aliyuncs.com
-
-
-
listener: TTInitListener. The SDK initialization callback. Use the callback to check the initialization status. This can be null.
TTCode
Code
Description
TT_SUCCESS
0
SDK initialized successfully.
TT_NOT_INIT
-1
SDK not initialized.
TT_NOT_PERMISSION
-2
The SDK has not been granted the required Android permissions.
TT_UNKNOWN_ERROR
-3
Unknown system error.
TT_NETWORK_ERROR
-4
Network error.
TT_NETWORK_ERROR_EMPTY
-5
Network error: empty response.
TT_NETWORK_ERROR_INVALID
-6
Invalid network response format.
TT_PARSE_SRV_CFG_ERROR
-7
Failed to parse server configuration.
TT_NETWORK_RET_CODE_ERROR
-8
Gateway returned a failure response.
TT_APPKEY_EMPTY
-9
Empty appkey.
TT_PARAMS_ERROR
-10
Invalid parameter.
TT_FGKEY_ERROR
-11
Key calculation error.
TT_APPKEY_ERROR
-12
SDK version and appkey mismatch.
-
-
Return value: int. Returns 0 on success, or -1 on failure.
-
Sample code:
// The appkey is the authentication key assigned on the Alibaba Cloud platform. final String appkey="******"; // Optional parameters to configure IPv6 and international reporting. Map<String, String> options = new HashMap<>(); options.put("IPv6", "0"); // Use IPv4. options.put("Intl", "0"); // Report to the Chinese mainland. //options.put("Intl", "1"); // Report to a region outside the Chinese mainland. // Report to US (Silicon Valley). //options.put("CustomUrl", "https://cloudauth-device.us-west-1.aliyuncs.com"); //options.put("CustomHost", "cloudauth-device.us-west-1.aliyuncs.com"); // An initial data collection gathers device information one time. You can call the init function again to perform a new collection based on your business requirements. // Full collection. int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_DEFAULT, options, null); // Specify privacy data to collect. You can combine different flags by using the "|" operator. 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);
-
-
Hash data.
This custom signing method calculates a hash of the input data and returns the generated
whashstring as the custom signature data. For POST, PUT, and PATCH requests, pass the request body. For GET and DELETE requests, pass the full URL. Thewhashstring must be added to the ali_sign_whash field in the HTTP request header.// Request type: public enum RequestType { GET, POST, PUT, PATCH, DELETE } /** * Hashes data for a custom signature. * * @param type The request type. * @param input The data to hash. * @return The whash string. */ public static String vmpHash(RequestType type, byte[] input);-
Parameters:
-
type: RequestType. The request type. Valid values:
-
GET: A GET request.
-
POST: A POST request.
-
PUT: A PUT request.
-
PATCH: A PATCH request.
-
DELETE: A DELETE request.
-
-
input: byte[]. The data to hash.
-
Return value: String. Returns the
whashstring. -
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);NoteThis method is not required for default signing. For custom signing, call this method to hash the data before signing it.
-
-
Sign data.
This method uses VMP technology to sign the input data and returns a
wtokenstring for request authentication./** * Signs the data. * * @param type The signature type. * @param input The data to sign. * @return The wtoken string. */ public static String vmpSign(int type, byte[] input);-
Parameters:
-
type: int. The data signing type. The value must be 1.
-
input: byte[]. The data to sign. This is typically the entire request body or the whash string from custom signing.
-
-
Return value: String. Returns the
wtokenstring. -
Sample code:
// Use this code if default signing is configured in the console (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); // Use this code if custom signing is configured in the console. // 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);Note-
When you call vmpHash for custom signing, the input parameter of the vmpSign method is the generated
whashstring. When you configure app anti-bot policies, the value of the Custom Signature Field must be set toali_sign_whash. -
When you call vmpHash to generate a
whashstring 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, as some frameworks automatically encode Chinese characters or parameters. -
The input parameter of the vmpHash method does not support an empty byte array or an empty string. If the input is a URL, it must contain a path or parameters.
-
When you call vmpSign, if the request body is empty (for example, in a GET request or a POST request with an empty body), pass a null object or the byte value of an empty string, such as "".getBytes("UTF-8").
-
If the
whashorwtokenvalue is one of the following strings, the SDK has returned an error:-
you must call init first: The SDK is not initialized. Call
init()first. -
you must input correct data: Invalid input data.
-
you must input correct type: Invalid input type.
-
-
-
3. Perform secondary verification
-
Evaluate the result.
Check the cookie and body fields in the response to determine if secondary verification is needed. Before calling this method, merge multiple Set-Cookie entries into a single cookie string.
/** * Determines whether to perform secondary verification. * * @param cookie The cookie string. * @param body The body string. * @return 0 for pass, or 1 for secondary verification required. */ public static int cptCheck(String cookie, String body)-
Parameters:
-
cookie: String. All cookies from the HTTP response.
-
body: String. The entire body from the HTTP response.
-
-
Return value: int. Returns 0 if the request passed, or 1 if secondary verification is required.
-
Sample code:
String cookie = "key1=value1;kye2=value2;"; String body = "...."; int recheck = TigerTallyAPI.cptCheck(cookie, body); Log.d("AliSDK", "recheck:" + recheck);
-
-
Create a slider.
If cptCheck returns 1, create a slider object. The TTCaptcha object provides the
show()anddismiss()methods to display and hide the slider window, respectively. TTOption encapsulates the slider's configurable parameters, and TTListener provides callbacks for success and failure states. To use a custom slider window, pass the URL of the custom page. Both local HTML files and remote pages are supported./** * Creates a slider object. * * @param activity The activity where the slider is displayed. * @param option The slider parameters. * @param listener The callback for the slider. * @return The slider verification object. */ public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener); /** * The slider object. */ public class TTCaptcha { /** * Displays the slider. */ public void show(); /** * Hides the slider. */ public void dismiss(); /** * Gets the slider traceId for data statistics. */ public String getTraceId(); } /** * The slider parameters. */ public static class TTOption { // Specifies whether the slider can be dismissed by clicking a blank area. public boolean cancelable; // The custom page. Local HTML files and remote URLs are supported. public String customUri; // The language. public String language; } /** * The callback for slider events. */ public interface TTListener { /** * Called on successful verification. * * @param captcha The slider object. * @param data The token. Defaults to certifyId. */ void success(TTCaptcha captcha, String data); /** * Called on verification failure. * * @param captcha The slider object. * @param code The error code. */ void failed(TTCaptcha captcha, String code); }-
Parameters:
-
activity: Activity. The current page activity.
-
option: TTOption. The slider configuration parameters.
-
listener: TTListener. The slider status callback.
-
-
Return value: TTCaptcha. The slider object.
-
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();NoteA verification failure indicates that an exception was detected after the user completed the slider challenge.
The following list describes the error codes:
-
1001: Verification failed.
-
1002: System exception.
-
1003: Invalid parameter.
-
1005: Verification canceled.
-
8001: Failed to display the slider.
-
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(() -> {
// Initialize the SDK.
Map<String, String> options = new HashMap<>();
//options.put("Intl", "1"); // To enable international reporting.
// Use full data collection mode.
int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
// Alternatively, 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);
// Wait for initialization to complete.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Sign the data.
String data = "hello world";
String whash = null, wtoken = null;
// Use custom signing.
whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);
// Use standard signing.
// wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
// Log.d(TAG, "tiger tally vmp: " + wtoken);
// Send the request.
doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
// Check if the slider is required.
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);
}
}