When the Do not terminate the App on detecting Threat option is enabled, the SDK will not force-close the app. Use the sample code in this guide to track threat events and decide how to respond. If the option is not enabled, the SDK terminates the app internally and the sample code handles the alert popup.
generate_hash, the server applies the most recently downloaded SDK setting for that project — for all team members. If teammates download with different settings, the last download wins. Align your team on this setting before downloading.The sample code in Code Samples.txt (included in the SDK) provides ready-to-use handlers for each supported framework. You can use the code as-is, or extend it to collect additional data — such as user ID, device fingerprint, or threat type — before deciding how to respond.
How It Works
Each platform's sample code detects threats via a bitmask value (tamper / security_threat / ret). The exit call (_exit(0) or ExitApp()) happens inside the alert's confirmation handler. Insert your custom logic there, immediately before the exit call.
| Framework | How to get the sample code |
|---|---|
| Xcode (Swift), Xcode (Obj-C), Flutter, React Native | Copy from Code Samples.txt in the SDK and paste into the appropriate file in your project |
| Unreal Engine | AppSealingPlugin.cpp is already included in the SDK — open and edit it directly |
Typical customization flow:
- Paste the sample code into your project (non-Unreal frameworks)
- Collect the data you need (user ID, device fingerprint, threat flags, timestamp, etc.)
- Send it to your server using a fire-and-forget request — the app exits right after
- Optionally revoke the user's session server-side
- Let the original exit call proceed
_exit(0) may not complete. If guaranteed delivery is critical, call your endpoint before showing the alert, or implement a server-side session timeout as a fallback.Detected Threat Flags
The tamper value is a bitmask — each bit corresponds to a specific threat:
| Flag | Description |
|---|---|
kAppSealingErrorJailbreakDetected | Device is jailbroken |
kAppSealingErrorDRMDecrypted | Executable is not encrypted |
kAppSealingErrorDebugAttached | Debugger is attached |
kAppSealingErrorHashInfoCorrupted / kAppSealingErrorHashModified | App integrity corrupted |
kAppSealingErrorCodesignCorrupted / kAppSealingErrorExecutableCorrupted | Executable corrupted |
kAppSealingErrorCertificateChanged | App has been re-signed |
kAppSealingErrorBlacklistCorrupted | Blacklist/whitelist missing or corrupted |
kAppSealingErrorCheatToolDetected | Cheat tool detected |
Customization by Framework
Click a framework to expand the instructions.
▶ Xcode (Swift)
Paste the sample code into ViewController.swift. The customization point is inside the UIAlertAction handler, before _exit(0).
inst._IsAbnormalEnvironmentDetectedAsync { tamper in
if ( tamper > 0 ) {
// ... build msg string (keep as-is) ...
let alertController = UIAlertController(title: "AppSealing", message: msg, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Confirm", style: .default,
handler: { (action: UIAlertAction!) -> Void in
#if !DEBUG
// ▼ ADD YOUR CUSTOM LOGIC HERE ▼
let userID = UserDefaults.standard.string(forKey: "userID") ?? "unknown"
let payload: [String: Any] = [
"userID": userID,
"threatFlags": tamper,
"timestamp": Date().timeIntervalSince1970
]
MyAnalyticsClient.sendSecurityEvent(payload) // fire-and-forget
// ▲ END CUSTOM LOGIC ▲
_exit(0)
#endif
}))
self.present(alertController, animated: true, completion: nil)
}
}Apply the same pattern inside _NotifySwizzlingDetected for swizzling detection events.
▶ Xcode (Objective-C)
Paste the sample code into ViewController.mm. The customization point is inside the UIAlertAction handler block, before _exit(0).
[inst _IsAbnormalEnvironmentDetectedAsync:^( int tamper ) {
if ( tamper > 0 ) {
// ... build msg string (keep as-is) ...
UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"Confirm"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
#if !DEBUG && !defined(DEBUG)
// ▼ ADD YOUR CUSTOM LOGIC HERE ▼
NSString *userID = [[NSUserDefaults standardUserDefaults]
stringForKey:@"userID"] ?: @"unknown";
NSDictionary *payload = @{
@"userID": userID,
@"threatFlags": @(tamper),
@"timestamp": @([[NSDate date] timeIntervalSince1970])
};
[MyAnalyticsClient sendSecurityEvent:payload]; // fire-and-forget
// ▲ END CUSTOM LOGIC ▲
_exit(0);
#endif
}];
[alert addAction:confirm];
[self presentViewController:alert animated:YES completion:nil];
}
}];Apply the same pattern inside _NotifySwizzlingDetected for swizzling detection events.
▶ Flutter
Paste the sample code into AppDelegate.swift (which inherits FlutterAppDelegate). The structure is identical to the Swift sample, with one difference: the alert must be presented via DispatchQueue.main.async.
inst._IsAbnormalEnvironmentDetectedAsync { tamper in
if ( tamper > 0 ) {
// ... build msg string (keep as-is) ...
let alertController = UIAlertController(title: "AppSealing", message: msg, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Confirm", style: .default,
handler: { (action: UIAlertAction!) -> Void in
#if !DEBUG
// ▼ ADD YOUR CUSTOM LOGIC HERE ▼
let userID = UserDefaults.standard.string(forKey: "userID") ?? "unknown"
let payload: [String: Any] = [
"userID": userID,
"threatFlags": tamper,
"timestamp": Date().timeIntervalSince1970
]
MyAnalyticsClient.sendSecurityEvent(payload) // fire-and-forget
// ▲ END CUSTOM LOGIC ▲
_exit(0)
#endif
}))
DispatchQueue.main.async {
self.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
}Tip: Prefer calling your server directly from the Swift layer rather than routing through a Flutter method channel — the app is about to exit and the channel call may not complete.
▶ React Native
Paste the sample code into index.js. There are two detection points — apply customization to both.
1. Initial check (checkSecurityThreat)
{ text: "Confirm", onPress: () => {
// ▼ ADD YOUR CUSTOM LOGIC HERE ▼
fetch('https://your-server.com/security-event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userID: getUserID(),
threatFlags: security_threat,
timestamp: Date.now()
})
}).catch(() => {}); // fire-and-forget
// ▲ END CUSTOM LOGIC ▲
AppSealingInterfaceBridge.ExitApp();
}}2. Periodic check (AppSealingPeriodicCheck)
{ text: "Confirm", onPress: () => {
// ▼ ADD YOUR CUSTOM LOGIC HERE ▼
fetch('https://your-server.com/security-event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userID: getUserID(),
threatCode: ret,
timestamp: Date.now()
})
}).catch(() => {});
// ▲ END CUSTOM LOGIC ▲
AppSealingInterfaceBridge.ExitApp();
}}Note: The sample checks Platform.OS === 'ios' before calling detection functions. Android behavior is handled separately by the native Android SDK layer.
▶ Unreal Engine
AppSealingPlugin.cpp is already included in the SDK. The alert dialog and exit(0) call are wrapped in a #if 0 block (disabled by default).
To enable and customize:
- Open
AppSealingPlugin.cppfrom the SDK - Change
#if 0to#if 1 - Add your custom logic before
exit(0)
AsyncTask(ENamedThreads::GameThread, [Fs, tamper]()
{
#if 1 // <-- Change from 0 to 1 to enable
// ▼ ADD YOUR CUSTOM LOGIC HERE ▼
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request =
FHttpModule::Get().CreateRequest();
Request->SetURL(TEXT("https://your-server.com/security-event"));
Request->SetVerb(TEXT("POST"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
FString Body = FString::Printf(TEXT("{\"threatFlags\":%d}"), tamper);
Request->SetContentAsString(Body);
Request->ProcessRequest(); // fire-and-forget
// ▲ END CUSTOM LOGIC ▲
FText title = FText::FromString(TEXT("Security Threat Detected"));
if (FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Fs), &title)
== EAppReturnType::Ok)
exit(0);
#endif
});Unreal_IsAbnormalEnvironmentDetected is a blocking call, already wrapped in AsyncTask(ENamedThreads::AnyThread, ...). Do not move it to GameThread directly — it will block the render loop.
What Information Can You Collect
The following are examples only. Collect whatever information is relevant to your use case.
| Data | How to Get It |
|---|---|
| Detected threat type | tamper bitmask value (see flag table above) |
| User / account ID | Your app's own session or UserDefaults |
| Device ID | Unreal_GetAppSealingDeviceID (Unreal), or platform APIs (UIDevice, etc.) |
| App version | Bundle.main.infoDictionary (iOS), or your own constant |
| Timestamp | Date() / Date.now() |