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

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):
| Version | Framework file | Use when |
|---|---|---|
| IDFA | AliTigerTally_IDFA.framework | Your app uses IDFA |
| Non-IDFA | AliTigerTally_NOIDFA.framework | Your app does not use IDFA |
Step 1: Create a project
Create a new iOS project in Xcode and complete the setup wizard.

Step 2: Add the framework files
Add the main SDK framework to your project. Choose the version that matches your IDFA requirements.
IDFA version:

Non-IDFA version:

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

Add the resource bundle AliCaptcha.bundle to your project:

Step 3: Add dependency libraries
Add the following libraries under Link Binary With Libraries in your target's build phases:
| Library | IDFA version | Non-IDFA version |
|---|---|---|
libc++.tbd | Yes | Yes |
libresolv.9.tbd | Yes | Yes |
CoreTelephony.framework | Yes | Yes |
AdSupport.framework | Yes | No |
AppTrackingTransparency.framework | Yes | No |

Step 4: Configure linker flags
In Build Settings, add -ObjC to Other Linker Flags:

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:
| Mode | collectType value | What it collects |
|---|---|---|
| Full | TT_DEFAULT | All device data |
| Custom privacy | TT_NO_BASIC_DATA, TT_NO_UNIQUE_DATA, TT_NO_EXTRA_DATA (combinable with |) | Partial data — excludes the specified categories |
| Non-privacy | TT_NOT_GRANTED | No 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 flag | Excludes |
|---|---|
TT_NO_BASIC_DATA | Device name, system version, screen resolution |
TT_NO_UNIQUE_DATA | IDFV and IDFA |
TT_NO_EXTRA_DATA | Connected 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:
| Parameter | Type | Description |
|---|---|---|
appkey | NSString | Your AppKey from the Bot Management console |
collectType | TTCollectType | Data collection mode (see table above) |
options | NSMutableDictionary | Optional reporting configuration (see table below). Default: nil |
onInitFinish | TTInitListener | Initialization callback. Default: nil |
`options` configuration:
| Key | Values | Description |
|---|---|---|
IPv6 | 0 (default), 1 | 0: use IPv4. 1: use IPv6. |
Intl | 0 (default), 1 | 0: report from the Chinese mainland. 1: report from outside the Chinese mainland. |
CustomUrl | URL string | Custom reporting server URL (for specific regions, e.g., https://cloudauth-device.us-west-1.aliyuncs.com) |
CustomHost | Hostname string | Custom reporting server host (e.g., cloudauth-device.us-west-1.aliyuncs.com) |
For most international regions, setIntlto1. SetCustomUrlandCustomHostonly if you need to report to a specific region, such as US (Silicon Valley).
Returns: int — 0 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];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 headerCustom 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
vmpHashFor 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 tovmpHashmust exactly match the final request URL. Some frameworks automatically encode Chinese characters or query parameters — verify there are no encoding differences.vmpHashdoes 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 multipleSet-Cookiefields. Merge them intokey1=value1;key2=value2;format before passing tocptCheck.
Example:
NSString *cookie = @"key1=value1;key2=value2;";
NSData *body = /* response body */;
int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
// recheck == 1: show CAPTCHA sliderShow 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:
| Property | Type | Description |
|---|---|---|
cancelable | BOOL | Allow users to dismiss the slider by tapping outside it |
customUri | NSString | Path to a custom verification page (local HTML file or remote URL) |
language | NSString | Display 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;
@endExample:
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
| Code | Constant | Description |
|---|---|---|
0 | TT_SUCCESS | Initialization succeeded |
-1 | TT_NOT_INIT | init was not called |
-2 | TT_NOT_PERMISSION | Required iOS permissions are not granted |
-3 | TT_UNKNOWN_ERROR | Unknown system error |
-4 | TT_NETWORK_ERROR | Network error |
-5 | TT_NETWORK_ERROR_EMPTY | Network error — response body is empty |
-6 | TT_NETWORK_ERROR_INVALID | Network error — response format is invalid |
-7 | TT_PARSE_SRV_CFG_ERROR | Failed to parse server configuration |
-8 | TT_NETWORK_RET_CODE_ERROR | Gateway returned a failure response |
-9 | TT_APPKEY_EMPTY | AppKey is empty |
-10 | TT_PARAMS_ERROR | Parameter error |
-11 | TT_FGKEY_ERROR | Key calculation error |
-12 | TT_APPKEY_ERROR | SDK version does not match AppKey version |
CAPTCHA error codes
| Code | Description |
|---|---|
1001 | Verification failed (slider exception during or after user interaction) |
1002 | System exception |
1003 | Parameter error |
1005 | Verification canceled by user |
8001 | Failed to invoke the slider |
8002 | Slider verification data is abnormal |
8003 | Internal exception during slider verification |
8004 | Network 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"—initwas not called before signing. Callinitand wait at least 2 seconds before callingvmpSign."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 tovmpHashis 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