All Products
Search
Document Center

Web Application Firewall:iOS app SDK integration

Last Updated:Mar 31, 2026

Integrate the Web Application Firewall (WAF) App Protection SDK into your iOS app to enable request signing and bot protection. After integration, WAF verifies request signatures to detect and block malicious traffic.

Prerequisites

Before you begin, ensure that you have:

  • An iOS app running iOS 9.0 or later (earlier versions are not supported)

  • The WAF App Protection SDK for iOS — submit a ticket to get it from a product technical expert

  • The SDK authentication key (AppKey) — after you enable Bot Management, go to Bot Management > App Protection and click Obtain and Copy AppKey in the app list

image
The SDK package is named tigertally-X.Y.Z-xxxx-ios.zip (where X.Y.Z is the version number) and contains two framework files and two xcframework files. Each Alibaba Cloud account has one AppKey that applies to all WAF-protected domains and works across Android, iOS, and HarmonyOS integrations.

Example AppKey: **OpKLvM6zliu6KopyHIhmneb_u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK**

Choose an SDK version

The iOS SDK comes in two versions. Choose based on whether your app uses the identifier for advertisers (IDFA):

VersionFramework fileUse when
IDFAAliTigerTally_IDFA.frameworkYour app uses IDFA
Non-IDFAAliTigerTally_NOIDFA.frameworkYour app does not use IDFA

Step 1: Create a project

Create a new iOS project in Xcode and complete the setup wizard.

image.png

Step 2: Add the framework files

Add the main SDK framework to your project. Choose the version that matches your IDFA requirements.

IDFA version:

image.png

Non-IDFA version:

image.png

Add the CAPTCHA module framework AliCaptcha.framework to your project:

image.png

Add the resource bundle AliCaptcha.bundle to your project:

image.png

Step 3: Add dependency libraries

Add the following libraries under Link Binary With Libraries in your target's build phases:

LibraryIDFA versionNon-IDFA version
libc++.tbdYesYes
libresolv.9.tbdYesYes
CoreTelephony.frameworkYesYes
AdSupport.frameworkYesNo
AppTrackingTransparency.frameworkYesNo
image.png

Step 4: Configure linker flags

In Build Settings, add -ObjC to Other Linker Flags:

image.png

Step 5: Add integration code

Import the header file

IDFA version:

#import <AliTigerTally_IDFA/AliTigerTally.h>

Non-IDFA version:

#import <AliTigerTally_NOIDFA/AliTigerTally.h>

Initialize the SDK

Call init once at app start to collect device information. The SDK supports three data collection modes — choose one based on your privacy requirements:

ModecollectType valueWhat it collects
FullTT_DEFAULTAll device data
Custom privacyTT_NO_BASIC_DATA, TT_NO_UNIQUE_DATA, TT_NO_EXTRA_DATA (combinable with |)Partial data — excludes the specified categories
Non-privacyTT_NOT_GRANTEDNo privacy fields (excludes IDFA and identifier for vendors (IDFV))
Complete data improves threat detection. Choose a mode that meets your privacy compliance requirements while collecting as much data as possible.

Data collection category reference:

collectType flagExcludes
TT_NO_BASIC_DATADevice name, system version, screen resolution
TT_NO_UNIQUE_DATAIDFV and IDFA
TT_NO_EXTRA_DATAConnected Wi-Fi information (SSID, BSSID) and nearby Wi-Fi networks

`init` method signature:

- (int)init:(NSString *)appkey
    collectType:(TTCollectType)type
    options:(NSMutableDictionary *_Nullable)options
    listener:(TTInitListener _Nullable)onInitFinish;

Parameters:

ParameterTypeDescription
appkeyNSStringYour AppKey from the Bot Management console
collectTypeTTCollectTypeData collection mode (see table above)
optionsNSMutableDictionaryOptional reporting configuration (see table below). Default: nil
onInitFinishTTInitListenerInitialization callback. Default: nil

`options` configuration:

KeyValuesDescription
IPv60 (default), 10: use IPv4. 1: use IPv6.
Intl0 (default), 10: report from the Chinese mainland. 1: report from outside the Chinese mainland.
CustomUrlURL stringCustom reporting server URL (for specific regions, e.g., https://cloudauth-device.us-west-1.aliyuncs.com)
CustomHostHostname stringCustom reporting server host (e.g., cloudauth-device.us-west-1.aliyuncs.com)
For most international regions, set Intl to 1. Set CustomUrl and CustomHost only if you need to report to a specific region, such as US (Silicon Valley).

Returns: int0 on success, negative on failure.

Example:

NSString *appKey = @"<your-appkey>";

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setValue:@"0" forKey:@"IPv6"];  // Use IPv4
[options setValue:@"0" forKey:@"Intl"];  // Report from the Chinese mainland
// [options setValue:@"1" forKey:@"Intl"];  // Report from outside the Chinese mainland

// To report to US (Silicon Valley):
// [options setValue:@"https://cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomUrl"];
// [options setValue:@"cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomHost"];

// Full data collection
if (0 == [[AliTigerTally sharedInstance] init:appKey collectType:TT_DEFAULT options:options listener:nil]) {
    NSLog(@"Initialization successful");
} else {
    NSLog(@"Initialization failed");
}

// Custom privacy: exclude basic and extended device data
TTCollectType collectPrivacy = TT_NO_BASIC_DATA | TT_NO_EXTRA_DATA;
[[AliTigerTally sharedInstance] init:appKey collectType:collectPrivacy options:options listener:nil];

// Non-privacy: exclude all privacy fields
[[AliTigerTally sharedInstance] init:appKey collectType:TT_NOT_GRANTED options:options listener:nil];
Important

Wait at least 2 seconds after calling init before calling vmpSign. This interval is a recommendation, not a requirement, and is intended to improve the effectiveness of the SDK's protection. You can adjust this interval based on your needs, but a shorter interval may reduce protection quality.

Set the user account (optional)

Call setAccount to associate a desensitized user identifier with requests. This enables more granular WAF mitigation policies.

/**
 * Sets the user account.
 * @param account  A desensitized string identifying the user.
 */
- (void)setAccount:(NSString *)account;

For guest users, skip this call and proceed with initialization. After a user logs in, call setAccount and then re-initialize:

[[AliTigerTally sharedInstance] setAccount:@"<desensitized-user-id>"];

Sign requests

All examples use vmpSign to generate a wtoken string and attach it to the wToken request header.

Default signing

Use default signing when you have not selected the custom signing option in the console.

Pass the request body to vmpSign. For requests with no body (such as GET), pass nil or an empty string encoded as NSData.

/**
 * Signs data using VMP technology.
 * @param input  The data to sign (request body, or nil for empty body).
 * @return The wtoken string for request authentication.
 */
- (NSString *)vmpSign:(NSData *)input;

Example:

NSString *body = @"hello world";
NSString *wtoken = [[AliTigerTally sharedInstance]
    vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
// Attach wtoken to the "wToken" HTTP request header

Custom signing

Use custom signing when you have selected the custom signing option in the console. Call vmpHash first to generate a whash, then pass the whash as input to vmpSign.

  • For POST, PUT, PATCH requests: pass the request body to vmpHash

  • For GET, DELETE requests: pass the full URL to vmpHash

Add the whash value to the ali_sign_whash request header. Also set Custom Signing Field to ali_sign_whash in your console scenario-based policy configuration.

/**
 * Hashes data for custom signing.
 * @param type   The HTTP method (TT_GET, TT_POST, TT_PUT, TT_PATCH, TT_DELETE).
 * @param input  The data to hash (request body for POST/PUT/PATCH; full URL for GET/DELETE).
 * @return The whash string.
 */
- (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;

POST request example:

NSString *body = @"hello world";

// Generate whash from the request body
NSString *whash = [[AliTigerTally sharedInstance]
    vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];

// Generate wtoken from the whash
NSString *wtoken = [[AliTigerTally sharedInstance]
    vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];

// Attach whash to "ali_sign_whash" header and wtoken to "wToken" header
NSLog(@"whash: %@, wtoken: %@", whash, wtoken);

GET request example:

NSString *url = @"https://tigertally.aliyun.com/apptest";

// Generate whash from the full URL
NSString *whash = [[AliTigerTally sharedInstance]
    vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]];

// Generate wtoken from the whash
NSString *wtoken = [[AliTigerTally sharedInstance]
    vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
The URL passed to vmpHash must exactly match the final request URL. Some frameworks automatically encode Chinese characters or query parameters — verify there are no encoding differences. vmpHash does not accept empty strings; for GET requests, the URL must include a path or query parameter.

Handle secondary verification

If WAF detects suspicious activity, it returns a CAPTCHA challenge. Check each response and show the CAPTCHA slider when required.

Check the response

Pass the response cookie and body to cptCheck after every request:

/**
 * Checks whether secondary verification is required.
 * @param cookie  All cookies from the response (merged into "key=value;" format).
 * @param body    The full response body.
 * @return 0: pass. 1: secondary verification required.
 */
- (int)cptCheck:(NSString *)cookie body:(NSData *)body;
The response headers may include multiple Set-Cookie fields. Merge them into key1=value1;key2=value2; format before passing to cptCheck.

Example:

NSString *cookie = @"key1=value1;key2=value2;";
NSData *body = /* response body */;
int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
// recheck == 1: show CAPTCHA slider

Show the CAPTCHA slider

When cptCheck returns 1, create and display a TTCaptcha slider:

/**
 * Creates a CAPTCHA slider for secondary verification.
 * @param view      The parent view.
 * @param option    Slider configuration.
 * @param delegate  Callback for verification results.
 * @return The TTCaptcha slider object.
 */
- (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate;

`TTOption` configuration:

PropertyTypeDescription
cancelableBOOLAllow users to dismiss the slider by tapping outside it
customUriNSStringPath to a custom verification page (local HTML file or remote URL)
languageNSStringDisplay language (e.g., @"cn" for Chinese, @"en" for English)

`TTDelegate` callbacks:

@protocol TTDelegate <NSObject>
@required
// Called when verification succeeds.
- (void)success:(TTCaptcha *)captcha data:(NSString *)data;

// Called when verification fails.
- (void)failed:(TTCaptcha *)captcha code:(NSString *)code;
@end

Example:

TTOption *option = [[TTOption alloc] init];
option.language   = @"cn";
option.cancelable = YES;

TTCaptcha *captcha = [[AliTigerTally sharedInstance]
    cptCreate:[self view] option:option delegate:self];
[captcha show];

// In TTDelegate callbacks:
- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
    NSLog(@"Verification passed: %@", data);
}

- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
    NSLog(@"Verification failed with code: %@", code);
}

Initialization status codes

CodeConstantDescription
0TT_SUCCESSInitialization succeeded
-1TT_NOT_INITinit was not called
-2TT_NOT_PERMISSIONRequired iOS permissions are not granted
-3TT_UNKNOWN_ERRORUnknown system error
-4TT_NETWORK_ERRORNetwork error
-5TT_NETWORK_ERROR_EMPTYNetwork error — response body is empty
-6TT_NETWORK_ERROR_INVALIDNetwork error — response format is invalid
-7TT_PARSE_SRV_CFG_ERRORFailed to parse server configuration
-8TT_NETWORK_RET_CODE_ERRORGateway returned a failure response
-9TT_APPKEY_EMPTYAppKey is empty
-10TT_PARAMS_ERRORParameter error
-11TT_FGKEY_ERRORKey calculation error
-12TT_APPKEY_ERRORSDK version does not match AppKey version

CAPTCHA error codes

CodeDescription
1001Verification failed (slider exception during or after user interaction)
1002System exception
1003Parameter error
1005Verification canceled by user
8001Failed to invoke the slider
8002Slider verification data is abnormal
8003Internal exception during slider verification
8004Network error

Troubleshooting

`vmpSign` or `vmpHash` returns an error string instead of a token

If whash or wtoken contains one of the following strings, an error occurred:

  • "you must call init first"init was not called before signing. Call init and wait at least 2 seconds before calling vmpSign.

  • "you must input correct data" — The input data is invalid. Check that the request body or URL is not empty or malformed.

  • "you must input correct type" — The HTTP method type passed to vmpHash is incorrect.

URL encoding mismatch in GET request signing

If the whash for a GET request is rejected, the URL passed to vmpHash may not match the actual request URL. Some networking frameworks automatically percent-encode query parameters or Chinese characters. Log both URLs and compare them character by character.

Initialization fails with `TT_NETWORK_ERROR` (`-4`)

Check that the device has network access and that the reporting domain is reachable. If you are deploying outside the Chinese mainland, set Intl to 1 in the options dictionary. For a specific regional endpoint, set CustomUrl and CustomHost.

Complete example

The following example shows the full integration flow — initialization, request signing, secondary verification check, and CAPTCHA display.

#import "DemoController.h"
#if __has_include(<AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>)
    #import <AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>
#else
    #import <AliTigerTally_IDFA/AliTigerTally_IDFA.h>
#endif

@interface DemoController () <TTDelegate>
@end

static NSString *kAppHost = @"******";
static NSString *kAppUrl  = @"******";
static NSString *kAppkey  = @"******";

@implementation DemoController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self doTest];
}

- (void)doTest {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        // Step 1: Initialize the SDK
        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        // [options setValue:@"1" forKey:@"Intl"];  // Uncomment for outside the Chinese mainland
        int code = [[AliTigerTally sharedInstance] init:kAppkey collectType:TT_DEFAULT options:options listener:nil];
        NSLog(@"tigertally init: %d", code);

        // Step 2: Wait at least 2 seconds before signing
        [NSThread sleepForTimeInterval:2.0];

        // Step 3: Sign the request (custom signing example)
        NSString *body = @"hello world";
        NSString *whash = [[AliTigerTally sharedInstance]
            vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
        NSString *wtoken = [[AliTigerTally sharedInstance]
            vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
        NSLog(@"tigertally vmp — whash: %@, wtoken: %@", whash, wtoken);

        // For default signing (no custom signing option selected):
        // wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];

        // Step 4: Send the request and check for secondary verification
        [self doPost:kAppUrl host:kAppHost whash:whash wtoken:wtoken
            body:[body dataUsingEncoding:NSUTF8StringEncoding]
            callback:^(NSInteger code, NSString *cookie, NSData *body) {
                int check = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
                NSLog(@"captcha check result: %d", check);
                if (check == 0) return;
                // Secondary verification required — show CAPTCHA on main thread
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    [self doShow];
                }];
            }];
    }];
    [thread start];
}

- (void)doShow {
    TTOption *option = [[TTOption alloc] init];
    option.language   = @"cn";
    option.cancelable = YES;
    TTCaptcha *captcha = [[AliTigerTally sharedInstance]
        cptCreate:[self view] option:option delegate:self];
    [captcha show];
}

- (void)doPost:(NSString *)url host:(NSString *)host
        whash:(NSString *)whash wtoken:(NSString *)wtoken
        body:(NSData *)body callback:(void(^)(NSInteger code, NSString *cookie, NSData *body))callback {
    NSURL *requestUrl = [NSURL URLWithString:url];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl
        cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    [request setValue:@"text/x-markdown" forHTTPHeaderField:@"Content-Type"];
    [request setValue:host forHTTPHeaderField:@"HOST"];
    [request setValue:wtoken forHTTPHeaderField:@"wToken"];
    if (whash) {
        [request setValue:whash forHTTPHeaderField:@"ali_sign_whash"];
    }
    request.HTTPMethod = @"POST";
    request.HTTPBody = body;

    NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession]
        dataTaskWithRequest:request
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                callback(-1, nil, [[error description] dataUsingEncoding:NSUTF8StringEncoding]);
                return;
            }
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            NSMutableString *cookies = [[NSMutableString alloc] init];
            for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
                if ([url containsString:[cookie domain]]) {
                    [cookies appendFormat:@"%@=%@;", [cookie name], [cookie value]];
                }
            }
            callback(httpResponse.statusCode, cookies, data);
        }];
    [dataTask resume];
}

#pragma mark - TTDelegate

- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
    NSLog(@"captcha failed: %@", code);
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
        message:code preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
}

- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
    NSLog(@"captcha success: %@", data);
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
        message:data preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
}

@end