---
title: "Threat Detection Callback Integration"
description: "Receive real-time threat detection callbacks from a DoveRunner-protected Android app through an Android broadcast."
---
> For the complete documentation index, see [llms.txt](https://docs.doverunner.com/llms.txt).

When a protected Android app detects a threat whose action is set to **Callback**, DoveRunner Mobile App Security notifies the app in real time through an Android broadcast instead of terminating it. Your app can then respond in its own way — for example, by ending the user session, showing a warning screen, or reporting the event to your server.

Each broadcast carries two values:

- `threat_code` (String) — the code that identifies the detected threat
- `timestamp` (long) — the time of detection

## Requirements

- The app is sealed with sealing version **3.3.0.0 or higher**.
- **Callback** is selected as the action for each detection you want delivered to your app — in the sealing wizard, see [Threat Response Actions](https://docs.doverunner.com/mobile-app-security/android/android-sealing-options/#threat-response-actions); with the CLI, set the matching `action_*` option to `callback` (see [Threat Response Policies](https://docs.doverunner.com/mobile-app-security/android/cicd/basic-configuration/#threat-response-policies-from-3300)).
- A broadcast receiver is registered in the app, as described in [Integration Steps](#integration-steps).

**Caution:** 
If a detection is set to **Callback** but the app does not implement the receiver, the app runs in monitoring-only mode for that detection: the threat is detected and reported, but the app is **not terminated**.


## Broadcast Specification

| Item | Value |
| :--- | :---- |
| Action | `com.doverunner.aos.security.action.THREAT_DISCOVERED` |
| Required permission | `<ProtectedAppPackage>.doverunner.permission.RECEIVE_ALERTS` |

**Extras**

| Key | Type | Description |
| :-- | :--- | :---------- |
| `threat_code` | String | Code that identifies the detected threat. See [Threat Response Codes](#threat-response-codes). |
| `timestamp` | long | Time of detection, in milliseconds since the Unix epoch. |

## Integration Steps


1. **Declare the permission in `AndroidManifest.xml`.**

   The callback permission is generated per protected app package. Build the permission name from your app's package name:

   | Item | Value |
   | :--- | :---- |
   | Format | `<ProtectedAppPackage>.doverunner.permission.RECEIVE_ALERTS` |
   | Example: app package | `com.company.bank` |
   | Example: permission | `com.company.bank.doverunner.permission.RECEIVE_ALERTS` |

   ```xml
   <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.company.bank">

       <!-- Replace "com.company.bank" with your app's package name -->
       <permission
           android:name="com.company.bank.doverunner.permission.RECEIVE_ALERTS"
           android:protectionLevel="signature" />

       <uses-permission android:name="com.company.bank.doverunner.permission.RECEIVE_ALERTS" />

       <application>
           ...
       </application>

   </manifest>
   ```

2. **Register the broadcast receiver.**

   Register the receiver inside the `<application>` element and protect it with the permission declared in step 1.

   ```xml
   <application>

       <receiver
           android:name=".security.ThreatCallbackReceiver"
           android:exported="true"
           android:permission="com.company.bank.doverunner.permission.RECEIVE_ALERTS">
           <intent-filter>
               <action android:name="com.doverunner.aos.security.action.THREAT_DISCOVERED" />
           </intent-filter>
       </receiver>

   </application>
   ```

   **Caution:** 
   The broadcast action and the permission name are independent values, not a matching pair. The action string `com.doverunner.aos.security.action.THREAT_DISCOVERED` is the same for every app; only the permission name is unique to each protected app package.
   

3. **Implement the receiver.**

   The receiver reads `threat_code` and `timestamp` from the intent and passes them to your handling logic.

   **Java**


   ```java
   package com.company.bank.security;

   import android.content.BroadcastReceiver;
   import android.content.Context;
   import android.content.Intent;
   import android.util.Log;

   public class ThreatCallbackReceiver extends BroadcastReceiver {

       private static final String TAG = "ThreatCallbackReceiver";
       private static final String ACTION_THREAT_DISCOVERED =
               "com.doverunner.aos.security.action.THREAT_DISCOVERED";

       @Override
       public void onReceive(Context context, Intent intent) {
           if (intent == null) return;

           if (ACTION_THREAT_DISCOVERED.equals(intent.getAction())) {
               String threatCode = intent.getStringExtra("threat_code");
               long timestamp = intent.getLongExtra("timestamp", 0L);

               Log.i(TAG, "Threat callback received. threat_code=" + threatCode
                       + ", timestamp=" + timestamp);

               handleThreat(context, threatCode, timestamp);
           }
       }

       private void handleThreat(Context context, String threatCode, long timestamp) {
           if (threatCode == null) return;

           switch (threatCode) {
               case "D10001":
                   // Example: Root detected - log out the user and invalidate the session
                   break;
               case "D60007":
                   // Example: Signing certificate mismatch - show a warning screen
                   break;
               default:
                   // Generic handling for other supported threat codes
                   break;
           }
       }
   }
   ```

   
   **Kotlin**


   ```kotlin
   package com.company.bank.security

   import android.content.BroadcastReceiver
   import android.content.Context
   import android.content.Intent
   import android.util.Log

   class ThreatCallbackReceiver : BroadcastReceiver() {

       companion object {
           private const val TAG = "ThreatCallbackReceiver"
           private const val ACTION_THREAT_DISCOVERED =
               "com.doverunner.aos.security.action.THREAT_DISCOVERED"
       }

       override fun onReceive(context: Context, intent: Intent?) {
           if (intent == null) return

           if (intent.action == ACTION_THREAT_DISCOVERED) {
               val threatCode = intent.getStringExtra("threat_code")
               val timestamp = intent.getLongExtra("timestamp", 0L)

               Log.i(TAG, "Threat callback received. threat_code=$threatCode, timestamp=$timestamp")

               handleThreat(context, threatCode, timestamp)
           }
       }

       private fun handleThreat(context: Context, threatCode: String?, timestamp: Long) {
           when (threatCode) {
               "D10001" -> {
                   // Example: Root detected - log out the user and invalidate the session
               }
               "D60007" -> {
                   // Example: Signing certificate mismatch - show a warning screen
               }
               else -> {
                   // Generic handling for other supported threat codes
               }
           }
       }
   }
   ```

   

   **Tip:** 
   `onReceive()` runs on the main thread and must return quickly. Move longer work, such as reporting to your server, to WorkManager or use `goAsync()`.
   


## Testing

Use the [sample app](#sample-app-source-code) or your own build with the receiver integrated to verify that callbacks are delivered.


1. Install the sample app, or your app build with the receiver integrated.
2. Install the protected app, sealed with **Callback** selected for the detections you want to test.
3. Trigger a supported detection — for example, launch the app on a rooted device or an emulator.
4. Monitor the log output:

   ```bash
   adb logcat -s ThreatCallbackReceiver
   ```

   When a callback is received, a log line similar to the following appears:

   ```
   Threat callback received. threat_code=D10001, timestamp=1720000000000
   ```


## Behavior Notes

- Callbacks are delivered only for detections whose action is set to **Callback** at sealing time.
- Handle unrecognized `threat_code` values in a default branch, as in the sample code above.
- Code hooking detection never triggers a callback. It always follows the default protection flow (alert, app termination, and reporting).
- The same `threat_code` may be delivered more than once. Deduplicate based on `threat_code` and `timestamp` as needed.
- Signing certificate mismatch (`D60007`) triggers a callback whenever the signing certificate of the installed app differs from the expected certificate.

## Threat Response Codes

The table below lists, for each sealing wizard option that offers **Callback**, the matching CLI `action_*` option and the threat response codes delivered in `threat_code`.

| Sealing wizard option | CLI option | Detects | Threat response codes |
| :-------------------- | :--------- | :------ | :-------------------- |
| Integrity Check | `action_app_integrity` | App code or binary modified, or APK repackaged or re-signed | `D21003`, `D40001`, `D50001`, `D60001`, `D60002`, `D60004`, `D60005`, `D60006`, `D60007` |
| Rooting | `action_rooting` | Root access | `D10001`, `D10002`, `D10003`, `D14001` |
| Developer Options | `action_developer_options` | Developer options enabled | `D13001` |
| USB Debugging | `action_usb_debugging` | ADB / USB debugging enabled | `D13002` |
| Emulators | `action_emulator` | Emulator environment | `D11001` |
| Work Profile & Samsung Security Folder | `action_work_profile` | App running in a work profile or Samsung Secure Folder | `D12001` |
| Macro Tools | `action_macro` | Macro automation tools | `D21001`, `D21002`, `D30001`, `D30002`, `D30003`, `D30004` |
| Packet Attacks | `action_sniff` | Packet interception tools | `D21001`, `D21002`, `D30001`, `D30002`, `D30003`, `D30004` |
| Keylogger | `action_keylogger` | Keylogger apps | `D21001`, `D21002`, `D30001`, `D30002`, `D30003`, `D30004` |
| Overlay Windows | `action_overlay` | Overlay windows from other apps | `D60003` |

**Note:** 
Macro Tools, Packet Attacks, and Keylogger share the same threat response codes, so `threat_code` alone does not tell you which of the three detections triggered the callback. If your handling must differ between them, set only the detections you need to **Callback**.


## Sample App Source Code

A sample app that demonstrates how to integrate and receive threat callbacks is available for reference and testing.

[Download the sample app source code (ZIP)](https://drive.google.com/file/d/1Ho6iLaoN5bEVOlUjiJ3fo6OjL81ohw4W/view?usp=sharing)

**Caution:** 
The sample app link may change or become unavailable without notice. If the link does not work and you need the sample, contact the [Help Center](https://support.doverunner.com).
