Outage Codes
The outage_code and outage_category values in outage webhooks, how to detect permission revocation, and how outages open and close.
The data.outage_code field on every outage_started and outage_ended webhook is a machine-readable identifier of the outage reason.
The accompanying data.outage_category groups codes by what's responsible:
outage_category | What it means |
|---|---|
behavioural | Caused by worker action (denied permission, force-stopped app, airplane mode). |
os | Caused by the operating system (kill, suspend, crash). |
adversarial | Anti-fraud signal (mocked location, time tampering). |
sporadic | Transient hardware/environment issue (GPS unavailable). |
reachability | Server-side cannot reach the device (push failed, no token). |
outage_code | outage_label | outage_category | Description |
|---|---|---|---|
location_permissions_denied | Location permission denied | behavioural | Worker fully denied location permission. On iOS, a denial also sends location_permission_imprecise. |
location_permission_when_in_use_background | Background location permission not granted | behavioural | Worker granted "When In Use" only — app cannot start tracking from background. |
location_permission_imprecise | Location imprecise | behavioural | Worker downgraded to approximate location instead of precise. On iOS, a denial also sends location_permissions_denied. |
location_permission_not_determined | Location permission unknown | behavioural | Permission state is unset (e.g. "Allow Once" reset, or never prompted). |
location_permission_restricted | Location permission restricted | behavioural | Permission blocked by Screen Time, parental controls, or MDM policy. |
location_services_disabled | Location services disabled | behavioural | Phone-level Location Services toggle is off. |
location_unavailable | Location unavailable | sporadic | App could not get a fresh GPS fix (signal lost, indoors, airplane mode, etc.). |
location_unavailable_provisional_authorization | Location unavailable auth | reachability | App requested Always permission without first asking When In Use; iOS limits authorization. |
location_mocked | Location mocked | adversarial | A mock-location app was active on the device. |
airplane_mode_detected | Device in airplane mode | behavioural | Worker enabled airplane mode. |
notifications_permission_denied | Notification permission denied | behavioural | Notifications disabled — push-driven start cannot be delivered. |
foreground_service_denied | Foreground service start prohibited | behavioural | Android blocked tracking from starting in the background. Opening the app clears it. |
tracking_service_terminated | Tracking service terminated | behavioural | App was force-stopped by worker or killed by OS battery management. |
sdk_killed_by_user | App killed by user | behavioural | Worker swiped the app away or used "Clear all". |
sdk_killed_permission_change | App killed by permission change | behavioural | Worker revoked a permission mid-tracking and the OS killed the app. |
sdk_killed_low_memory | Low memory | os | OS killed the app to reclaim memory. |
sdk_killed_crashed | App crashed | os | App crashed unexpectedly. |
sdk_killed_excessive_resource_usage | App killed — excessive resource usage | os | OS killed the app for using too much CPU, battery, or memory. |
sdk_killed_app_not_responsive | App killed for being unresponsive | os | OS killed the app because it stopped responding (ANR-style). |
sdk_killed_suspended | App suspended | os | OS suspended app execution while device was idle or locked. |
storage_unavailable | Storage unavailable | behavioural | Device storage is full; app cannot persist or send tracking data. |
push_notification_failed | Push notification failed | reachability | Server sent a push to start tracking but it didn't reach the device. |
push_token_missing_to_start_tracking | Push token missing | reachability | Push notification service didn't supply a messaging token to the app. |
Detecting permission revocation
To detect when a worker no longer has location permission to track, watch for an outage_started webhook where data.outage_category is behavioural and data.outage_code is one of:
location_permissions_deniedlocation_permission_when_in_use_backgroundlocation_permission_impreciselocation_permission_not_determinedlocation_permission_restrictedlocation_services_disabledforeground_service_denied(Android)sdk_killed_permission_change(Android)
To know when the worker has resolved the issue, wait for the next outage_ended on the same device. Do not match it by outage_code. See How outages open and close.
How outages open and close
The platform keeps one open outage per device. Use these rules to pair outage_started and outage_ended webhooks:
- One open outage per device. An
outage_startedwith a differentoutage_codereplaces the open outage. The platform does not send anoutage_endedfor the outage that it replaced. - One
outage_endedcloses all open outages. The platform sendsoutage_endedone time, when the device sends a location again. Itsoutage_codeis the code of the most recentoutage_started. When you get it, close every open outage on that device. - A repeated code is the same outage. An
outage_startedwith the sameoutage_codewhile the outage is open means that the SDK reported the condition again. It is the same outage. Do not open a second one. - iOS sends two codes for a denial. When the worker denies location on iOS, the SDK reports
location_permission_impreciseandlocation_permissions_deniedwithin milliseconds, as twooutage_startedwebhooks. The platform does not send a webhook when precise location is available again. The singleoutage_endedcloses both.
Example: iOS worker denies location, then allows it again
These three payloads arrive as separate webhooks; they are shown here in one array. The payloads are shortened: location, outage_label, outage_description, and version are left out. The worker denies location. 17 ms later the second code arrives. When the device tracks again, one outage_ended arrives with the most recent code, location_permissions_denied. It closes both outages.
[
{
"type": "worker",
"device_id": "AB930CCD-50A9-4ECA-8758-D7CB09058606",
"data": {
"value": "outage_started",
"outage_category": "behavioural",
"outage_code": "location_permission_imprecise"
},
"recorded_at": "2024-11-28T12:45:00.000Z",
"worker_handle": "ram@123"
},
{
"type": "worker",
"device_id": "AB930CCD-50A9-4ECA-8758-D7CB09058606",
"data": {
"value": "outage_started",
"outage_category": "behavioural",
"outage_code": "location_permissions_denied"
},
"recorded_at": "2024-11-28T12:45:00.017Z",
"worker_handle": "ram@123"
},
{
"type": "worker",
"device_id": "AB930CCD-50A9-4ECA-8758-D7CB09058606",
"data": {
"value": "outage_ended",
"outage_category": "behavioural",
"outage_code": "location_permissions_denied"
},
"recorded_at": "2024-11-28T12:45:17.000Z",
"worker_handle": "ram@123"
}
]