All Products
Search
Document Center

Edge Security Acceleration:Integrating the protection SDK for Android applications

Last Updated:Sep 08, 2026

You must integrate the SDK into your application to configure scenario-specific rules for app bot management in the Bots console. This topic describes how to integrate the protection SDK into an Android application.

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

  • You have obtained the SDK for your Android application.

    To obtain the SDK, submit a ticket.

    Note

    The 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 (appkey).

    When you create a Bots rule set, click Obtain and Copy AppKey to get the SDK authentication key. This key is required for SDK initialization.

    image

Step 1: Create a project

In Android Studio, follow the configuration wizard to create a new Android project. The project directory structure is as follows:

tigertally-demo-apk [TigerTallyDemo]
  .gradle
  .idea
  app
    build
    libs
      AliCaptcha_xxx.aar
      AliTigerTally_xxx.aar
    src
      androidTest
      main
      test [unitTest]
    .gitignore
    build.gradle
    proguard-rules.pro
  gradle
    wrapper
      gradle-wrapper.jar
      gradle-wrapper.properties
  .gitignore
  .java-version
  build.gradle
  gradle.properties
  gradlew
  gradlew.bat
  local.properties
  settings.gradle
  tigertally.jks
  External Libraries
  Scratches and Consoles

Step 2: Integrate AAR

  1. 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).

  2. Open your app's build.gradle file and add dependencies on AliTigerTally_X.Y.Z.aar and AliCaptcha_X.Y.Z.aar from the libs directory.

    Important

    Replace 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"/>
Note

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 file

  • For the IDFA version, add the following import statement:

    #import <AliTigerTally_IDFA/AliTigerTally.h>
  • For the non-IDFA version, add the following import statement:

    #import <AliTigerTally_NOIDFA/AliTigerTally.h> 

2. Configure data signing

  1. Set a custom end-user identifier for your business. This allows you to configure WAF protection policies with greater flexibility.

    /**
    * Sets the user account.
    *
    * @param account   The account information.
    */
    - (void)setAccount:(NSString *)account;
    • Parameters:

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

    • Return value: None.

    • Example:

      // For guest users, you can skip setAccount and initialize directly. After a user logs in, call setAccount and re-initialize.
      [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
  2. Initialize the SDK to perform data collection.

    Each initialization collects device information once. You can call the init function again to re-initialize data collection for different business scenarios.

    Initialization supports three data collection modes: full, custom privacy, and non-privacy. The non-privacy mode excludes fields related to end-user privacy, such as IDFA and IDFV.

    Note

    To comply with your internal requirements, select a suitable data collection mode that ensures data integrity. More complete data improves threat detection.

    // Initialization callback. Returns the status code of the interface call.
    typedef void (^TTInitListener)(int);
    
    /**
     * Initializes the SDK.
     *
     * @param appkey        The key.
     * @param options       Optional parameters.
     * @param onInitFinish  The callback for when initialization is complete.
     * @return Indicates whether the initialization was successful.
     */
    - (int)init:(NSString *)appkey collectType:(TTCollectType)type options:(NSMutableDictionary *_Nullable)options listener:(TTInitListener _Nullable)onInitFinish;
    • Parameters:

      • appkey: Type: NSString. Your SDK authentication key.

      • collectType: Type: TTCollectType. The data collection mode. Valid values:

        Field

        Description

        Example

        TT_DEFAULT

        Collects all data.

        TT_DEFAULT

        TT_NO_BASIC_DATA

        Does not collect basic device data.

        This includes device name, OS version, and screen resolution.

        TT_NO_X | TT_NO_Y

        (where X and Y represent the data field types to exclude)

        TT_NO_UNIQUE_DATA

        Does not collect unique identifier data.

        This includes IDFV and IDFA.

        TT_NO_EXTRA_DATA

        Does not collect extended device data.

        This includes connected Wi-Fi information (SSID, BSSID) and the list of nearby Wi-Fi networks.

        TT_NOT_GRANTED

        Does not collect any of the preceding privacy-related data.

        TT_NOT_GRANTED

      • options: Type: NSMutableDictionary. Optional parameters for data collection. The default value is nil. The following parameters are available:

        Field

        Description

        Example

        IPv6

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

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

        • 1: Uses an IPv6 domain name.

        1

        Intl

        Specifies whether to use a domain name outside the Chinese mainland to report device information.

        • 0 (default): Reports to the Chinese mainland.

        • 1: Reports to regions outside the Chinese mainland.

        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 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 Intl is set to 0, data is reported to the default site in China (Shanghai): https://cloudauth-device.cn-shanghai.aliyuncs.com

        • If Intl is set to 1:

          • 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: Type: TTInitListener. The SDK initialization callback interface. You can use this callback to determine the status of the initialization. The default value is nil.

        TTCode

        Code

        Description

        TT_SUCCESS

        0

        Initialization successful.

        TT_NOT_INIT

        -1

        The SDK is not initialized.

        TT_NOT_PERMISSION

        -2

        Required iOS permissions have not been granted.

        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 network response is invalid.

        TT_PARSE_SRV_CFG_ERROR

        -7

        Failed to parse the server configuration.

        TT_NETWORK_RET_CODE_ERROR

        -8

        The gateway returned a failure response.

        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: Type: int. An error code. 0 indicates success, and a negative number indicates failure.

    • Example:

      // The appkey is the authentication key assigned to you on the Alibaba Cloud console.
      NSString *appKey = @"xxxxxxxxxxxxxxxxxxxxx";
      // Optional parameters. You can configure IPv6 and reporting from outside the Chinese mainland.
      NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
      [options setValue:@"0" forKey:@"IPv6"];     // Use IPv4.
      [options setValue:@"0" forKey:@"Intl"];     // Report data from within the Chinese mainland.
      // [options setValue:@"1" forKey:@"Intl"];  // Report data from outside the Chinese mainland.
      // Report data to the US (West) region.
      // [options setValue:@"https://cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomUrl"];
      // [options setValue:@"cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomHost"];
      
      // An initialization call collects device information once. You can call the init function again to collect information for different business scenarios.
      // Full data collection.
      if (0 == [[AliTigerTally sharedInstance] init:appkey collectType:TT_DEFAULT options:options listener:nil]) {
          NSLog(@"Initialization successful");
      } else {
          NSLog(@"Initialization failed");
      }
      
      // To collect specific types of private data, use the "|" operator to specify multiple types.
      TTCollectType collectPrivacy = TT_NO_BASIC_DATA | TT_NO_EXTRA_DATA;
      int ret = [[AliTigerTally sharedInstance] init:appkey collectType:collectPrivacy options:options listener:nil];
      
      // Do not collect private data.
      int ret = [[AliTigerTally sharedInstance] init:appkey collectType:TT_NOT_GRANTED options:options listener:nil];
  3. Data hashing.

    This custom signing interface hashes the input data to generate a whash string, which serves as a custom signature. For POST, PUT, and PATCH requests, use the request body as input. For GET and DELETE requests, use the full URL. You must also add the generated whash string to the ali_sign_whash field in the HTTP request header.

    Note

    To use the vmpHash function to generate custom signature data, you must configure the custom signature field as ali_sign_whash in the console. (Navigate to Bot Feature Recognition → Enable Custom Signature Field → Select Field Name as header → Set the Value to ali_sign_whash)

    // Request type:
    typedef NS_ENUM(NSInteger, TTRequestType) {
        TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE
    };
    
    /**
     * Hashes the custom signature data.
     * @param type  The data type.
     * @param input The data to be signed.
     * @return The whash string.
     */
    - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;
    • Parameters:

      • type: Type: TTTypeRequest. The data type. Valid values:

        • GET: GET request data.

        • POST: POST request data.

        • PUT: PUT request data.

        • PATCH: PATCH request data.

        • DELETE: DELETE request data.

      • input: Type: NSData. The data to be signed. Pass the body or URL based on the type.

    • Return value: Type: NSString. The whash string.

    • Example:

      // GET request
      NSString *url = @"https://tigertally.aliyun.com/apptest";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@", whash);
      
      // POST request
      NSString *body = @"hello world";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@", whash);
    Note

    Calling this interface is only required if you have enabled the custom signing option in the WAF console. If you use the default signature configuration, you can skip this step.

  4. Data signing.

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

    /**
     * Signs the data.
     * @param input The data to be signed.
     * @return The wtoken string.
     */
    - (NSString *)vmpSign:(NSData *)input;
    • Parameters:

      • input: Type: NSData. The data to be signed. This is typically the entire request body or the whash from custom signing.

    • Return value: Type: NSString. The wtoken string.

    • Example:

      // Default signature configured in the console (custom signing is not selected).
      NSString *body = @"hello world";
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"wtoken: %@", wtoken);
      
      // Custom signing configured in the console.
      // Custom signing for a POST request.
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
      
      // Custom signing for a GET request.
      NSString *url = @"https://tigertally.aliyun.com/apptest";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]];
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
      Note
      • When using custom signing, the input for the vmpSign interface must be the whash string generated by vmpHash. When you configure a scenario-based policy for App Protection, you must set the Custom Signing Field to ali_sign_whash.

      • When you call vmpHash to generate a whash for a GET request, ensure the input URL is identical to the final network request URL. Pay close attention to URL encoding, as some frameworks automatically encode characters or parameters.

      • The input for the vmpHash interface cannot be a byte array or an empty string. If the input is a URL, it must include a path or a parameter.

      • When calling vmpSign, if the request body is empty (for example, in a GET request or a POST request with no body), pass nil or the NSData representation of an empty string, such as [@"" dataUsingEncoding:NSUTF8StringEncoding].

      • If whash or wtoken returns one of the following strings, it indicates an error:

        • you must call init first: The init function was not called.

        • you must input correct data: The input data is invalid.

        • you must input correct type: The input type is invalid.

3. Secondary verification

  1. Check the result.

    To determine if secondary verification is needed, check the cookie and body from the response. If the header contains multiple Set-Cookie fields, merge them into a single cookie string before calling this interface.

    /**
     * Checks whether to perform secondary verification.
     *
     * @param cookie  The response cookie.
     * @param body    The response body.
     * @return 0: Pass, 1: Secondary verification required.
     */
    - (int)cptCheck:(NSString *)cookie body:(NSData *)body;
    • Parameters:

      • cookie: Type: NSString. All cookies in the request response.

      • body: Type: NSData. The entire body of the request response.

    • Return value: Type: int. The decision result. 0 indicates that the request passed, and 1 indicates that secondary verification is required.

      • Example:

      NSString *cookie = @"key1=value1;key2=value2;";
      NSData *body = xxx;
      int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
      NSLog(@"recheck: %d", recheck);
  2. Create a slider.

    If the result from cptCheck indicates a need for verification, create a slider object. The TTCaptcha object provides the show and dismiss methods to control the slider window. Use TTOption to configure its parameters and TTDelegate to handle its status callbacks. To customize the slider window, you must provide a URL to a custom page, which can be a local HTML file or a remote page.

    /**
     * Displays the slider for verification.
     *
     * @param view      The parent component.
     * @param option    The parameters.
     * @param detegate  The callback protocol.
     */
    - (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)detegate;
    
    
    @protocol TTDelegate <NSObject>
    @required
    // Slider verification is successful.
    - (void)success:(TTCaptcha *)captcha data:(NSString *)data;
    
    // Slider verification failed.
    - (void)failed:(TTCaptcha *)captcha code:(NSString *)code;
    @end
    
    
    @interface TTOption : NSObject
    // Tap to cancel.
    @property (nonatomic, assign) BOOL cancelable;
    
    // Custom page.
    @property (nonatomic, strong) NSString *customUri;
    
    // Language.
    @property (nonatomic, strong) NSString *language;
    @end
    
    
    @interface TTCaptcha : NSObject
    
    - (instancetype)init:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate;
    
    // Obtains the slider traceId for data analytics.
    - (NSString *)getTraceId;
    
    // Displays the slider.
    - (void)show;
    
    // Dismisses the slider.
    - (void)dismiss;
    
    @end
    • Parameters:

      • view: Type: View. The current page view.

      • option: Type: TTOption. The slider configuration parameters.

      • delegate: Type: TTDelegate. The slider status callback.

    • Return value: Type: TTCaptcha. The slider object.

    • Example:

      #pragma mark - TTDelegate
      - (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
          NSLog(@"captcha failed: %@", code);
      }
      
      - (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
          NSLog(@"captcha success: %@", data);
      }
      
      TTOption *option = [[TTOption alloc] init];
      // option.customUri = @"ali-tt-captcha-demo-ios";
      option.language   = @"cn";
      option.cancelable = true;
      
      TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self];
      [captcha show];
      Note

      A "Verification failed" error indicates that an exception was detected during or after user interaction with the slider.

      The following table describes the error codes.

      • 1001: Verification challenge failed.

      • 1002: A system exception occurred.

      • 1003: An invalid parameter was specified.

      • 1005: The verification was canceled.

      • 8001: Failed to display the slider.

      • 8002: An exception occurred in the slider verification data.

      • 8003: An internal exception occurred during slider verification.

      • 8004: A network error occurred.

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