# Stop polling. Get notified instantly.

Your fleet is making millions of API calls a day just to check if routers are online. Webhooks push that information to you the moment something changes.

[Browse the API reference](/developer/api)

Your fleet is making millions of API calls a day just to check if routers are online. Webhooks push that information to you the moment something changes.

| ❌ Polling every 60 seconds | ✓ Webhook on state change |
| --- | --- |
| 1,000 routers × 1 call/min × 60 min × 24 hrs | Only fires when a router actually changes state |
| 1.44M calls/day | ~50 calls/day |
| Delayed detection. Wasted API calls. Rate-limit pressure. | Instant notification. Zero wasted calls. |

## Why webhooks matter

### ⚡ Event-driven automation

Trigger workflows the moment something happens — not minutes later when a polling loop catches it. Router offline? Auto-create a ServiceNow ticket, notify on-call, and spin up a backup link before anyone checks a dashboard.

### 🤖 AIOps and agentic AI

AI agents need real-time signals to act on. Webhooks feed events directly into systems that diagnose, decide, and remediate.

### 🔗 Integration fabric

Connect NCM to ITSM, SIEM, observability platforms, Slack, Teams, PagerDuty, and custom dashboards.

### 📊 Real-time observability

Stream network events into your data pipeline and build dashboards that update the instant a device changes state.

## How webhooks work

NCM webhooks are built on the alert system:

- **Create a destination** — your URL plus a shared secret
- **Test it** — verify NCM can reach your server
- **Create an alert rule** — choose the events, attach the destination
 When an event fires, NCM sends an **HTTPS POST** to your URL and signs it with an `X-CP-Signature` header you can verify against your secret.

 **Webhooks are configured through the v2 API. There is no v3 webhook API.** Every call below authenticates with all four v2 key headers — `X-ECM-API-ID`, `X-ECM-API-KEY`, `X-CP-API-ID`, `X-CP-API-KEY`. **All four are required**; any subset returns `401`.

 **Every v2 URL must end in a trailing slash.** Without it the call is redirected and **both** calls count against your account's API quota.

## Setup guide

**Step 1**

### Create a webhook destination

```json
{
  "name": "My Monitoring Server",
  "endpoint": {
    "url": "https://my-server.example.com/webhooks/ncm"
  },
  "authentication": {
    "secret": "thisIsAUniqueValueNoOneWillBeAble2Guess"
  }
}
```

```http
POST https://www.cradlepointecm.com/api/v2/alert_push_destinations/
Content-Type: application/json
```

`endpoint` and `authentication` are **nested objects**, not flat strings. Save the `destination_config_id` from the response — Step 3 needs it.

 Or in the NCM UI: **Alerts & Logs → Push Destinations → Add Destination**.

**Step 2**

### Test the destination

```json
{
  "destination_config_id": "9b98af58-6703-11eb-8aef-42edcb7bf284"
}
```

```http
POST https://www.cradlepointecm.com/api/v2/test_alert_push_destinations/
Content-Type: application/json
```

`destination_config_id` is the **only** field this endpoint accepts, and it is required. Do not send the secret — NCM already holds it from Step 1.

 A **`201 Created`** with an empty body means your destination is reachable.

**Step 3**

### Create an alert rule
Alert types go inside **`filter_criteria.alert_type__in`**. There is no `alert_types` field, and `alert_rules` has no `name` or `enabled` field.

```json
{
  "schedule": "daily",
  "filter_criteria": {
    "alert_type__in": ["connection_state"]
  },
  "http_destinations": ["9b98af58-6703-11eb-8aef-42edcb7bf284"]
}
```

```http
POST https://www.cradlepointecm.com/api/v2/alert_rules/
Content-Type: application/json
```

Or in the NCM UI: **Alerts & Logs → Alert Rules → Add Rule**.

**Step 4**

### Receive and verify webhooks
NCM signs every pushed message. The **`X-CP-Signature`** header carries a **SHA256 HMAC of the request body, keyed with your shared secret**. Verify it before trusting the payload.

```python
import hmac, hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
SHARED_SECRET = b"thisIsAUniqueValueNoOneWillBeAble2Guess"

@app.route("/webhooks/ncm", methods=["POST"])
def handle_webhook():
    raw = request.get_data()                     # exact bytes, before parsing
    expected = hmac.new(SHARED_SECRET, raw, hashlib.sha256).hexdigest()

    # constant-time comparison — do not use ==
    if not hmac.compare_digest(expected, request.headers.get("X-CP-Signature", "")):
        return jsonify({"error": "bad signature"}), 403

    payload = request.get_json()

    # Alerts arrive in a "data" array — iterate it rather than reading data[0].
    for record in payload.get("data", []):
        alert_type = record.get("type")               # the reliable discriminator
        info       = record.get("info") or {}
        details    = record.get("router_details") or {}

        router_id   = record.get("router")            # integer
        router_name = details.get("name")
        summary     = record.get("friendly_info")

        if alert_type == "connection_state":
            print(f"{router_name} ({router_id}) is {info.get('state')} — {summary}")

    return jsonify({"status": "ok"}), 200

if __name__ == "__main__":
    app.run(port=5000)
```

Two things the legacy example gets wrong and this one fixes: sign the **raw bytes** (re-serialising parsed JSON changes them and the signature will never match), and compare with `hmac.compare_digest` rather than `!=`, which leaks timing information.

### Destination fields

| Field | Type | Required | Access | Notes |
| --- | --- | --- | --- | --- |
| name | string | POST | Read/Write | Display name |
| endpoint | JSON | POST | Read/Write | Flat dict, single key url |
| authentication | JSON | POST | Read/Write | Flat dict, single key secret |
| enabled | boolean | no | Read/Write | Whether NCM delivers to this destination |
| suspended | boolean | no | Read/Write | Connectivity state — see below |
| destination_config_id | timeuuid | PATCH, DELETE | Read Only | Use in http_destinations . Filters: = , __in , __gt , __lt |
| last_error_at | timestamp | no | Read Only | When the last delivery failed |
| last_error_text | string | no | Read Only | Why the last delivery failed |
| account | url | no | Read Only |  |
| updated_at | timestamp | no | Read Only |  |
 **`endpoint`** supports only `url`. **You cannot save two destinations with the same URL** — NCM returns an error on POST or PATCH if you try.

 **`authentication`** supports only `secret`, limited to **256 characters**. Use something unguessable, such as a UUID. On GET it always reads back as `"*"`.

 **`suspended`** is how NCM tells you it gave up. You may PATCH it from `true` to `false` to retry a destination that has since come back; **you cannot PATCH it from `false` to `true`.** When deliveries stop arriving, read the destination and check `suspended`, `last_error_at` and `last_error_text` before debugging your own server.

### Push alert states

| State | Meaning |
| --- | --- |
| active | Delivery is succeeding; alerts stream as they are generated |
| unreachable | Delivery is failing and NCM is making automatic retry attempts |
| suspended | NCM turned delivery off after it could not re-establish a connection |
| disabled | Delivery was switched off manually, via NCM or the API |

### Alert rule fields

| Field | Type | Required | Access | Notes |
| --- | --- | --- | --- | --- |
| filter_criteria | dictionary | POST | Read/Write | Two keys: alert_type__in , external_id__in |
| http_destinations | list(timeuuid) | POST | Read/Write | destination_config_id values |
| schedule | string | POST | Read/Write | "daily" or "hourly" |
| email_destinations | list of ints | no | Read/Write | User ids; NCM resolves them to email addresses |
| associated_accounts | list of ints | no | Read/Write | Scope the rule to accounts |
| associated_groups | list of ints | no | Read/Write | Scope the rule to groups |
| settings | json | no | Read/Write | Rule-specific settings, e.g. thresholds |
| alert_config_id | url | PATCH, DELETE | Read Only | Filters: = , __in , __gt , __lt |
| account | url | no | Read Only |  |
| updated_at | timestamp | no | Read Only |  |
 Some alert types support **thresholds** via `settings` — see the Alerting and Reporting documentation for which types and what values.

 Existing rules can read back `"schedule": null`. No source documents a value to send for un-batched delivery; confirm with the API team if you need it.

### Reading, updating and deleting

```http
GET    /api/v2/alert_push_destinations/?destination_config_id=<id>   # query param, not a path segment
PATCH  /api/v2/alert_push_destinations/<destination_config_id>/      # → 202, empty body
DELETE /api/v2/alert_push_destinations/<destination_config_id>/
PATCH  /api/v2/alert_rules/<alert_config_id>/                        # → 201, empty body
```

PATCH takes a partial body — `{"schedule": "hourly"}` or `{"endpoint": {"url": "https://new-host.example.com/hook"}}`.

### Delivered payload shape

Alerts arrive wrapped in a **`data` array** alongside a `meta` block. Every documented example carries one record, but `data` is an array, so iterate it rather than reading `data[0]`.

```json
{
  "data": [
    {
      "account": 1297836,
      "created_at": "2025-03-13T05:20:18.322318+00:00",
      "created_at_timeuuid": "da54838c-ffca-11ef-bd02-82acc788746c",
      "detected_at": "2025-03-13T05:20:18.322301+00:00",
      "friendly_info": "The device entered the \"offline\" state.",
      "info": { "state": "offline" },
      "router": 4716965,
      "type": "connection_state",
      "router_details": {
        "name": "OpenWRT-99b",
        "description": null,
        "mac": "46:E4:2E:D4:B0:30",
        "serial_number": "C551GW2276D4B004",
        "asset_id": null,
        "custom1": null,
        "custom2": null
      }
    }
  ],
  "meta": {
    "record_type": "alert",
    "record_version": "1.0.0",
    "deprecated": false
  }
}
```

### Record fields

| Field | Type | Notes |
| --- | --- | --- |
| type | string | The alert identifier. Use this to branch — see the caveat below |
| friendly_info | string | Human-readable summary; usually what you want in a ticket or chat message |
| router | integer | Router id — not a URL, unlike the /alerts/ API response |
| account | integer | Account id — not a URL |
| created_at | timestamp | When NCM generated the alert |
| created_at_timeuuid | timeuuid | Unique id for the alert; useful for deduplication |
| detected_at | timestamp | When the condition was detected on the device |
| info | object | Per-alert detail; contents vary by alert type |
| router_details | object | name , description , mac , serial_number , asset_id , custom1 , custom2 |
 `meta` carries `record_type` (`"alert"`), `record_version` (`"1.0.0"`) and `deprecated`. Check `record_version` if you want to guard against future shape changes.

 **Branch on the top-level `type`, not on anything inside `info`.**

 Across the 27 documented example payloads (26 alert types), top-level `type` is present in every one. `info` is not consistent:

- `info.type` is **absent** for `connection_state` and `wan_service_type`
- for the whole WAN family, `info.type` holds the *generic* value `wan_status_change` while `info.alert_type` holds the specific one (`ethernet_wan_disconnected`, `wwan_standby`, …)
- `info.state`, `info.status`, `info.threshold`, `info.usage` and others appear only for the alert types that have them
  There is no top-level `alert_type`, no top-level `state`, and no `router_name`. The router's name is at **`router_details.name`**; connection state is at **`info.state`**.

### What info contains, by family

| Alert family | Useful info keys |
| --- | --- |
| connection_state | state ( "online" / "offline" ) |
| WAN family (ethernet / modem / wwan) | status , alert_type , history , time , plus a nested info whose keys depend on the interface: ethernet port , mac , reason , uid , raw_port ; modem adds manufacturer , model , product , serial ; wwan has ssid , rssi , reason , uid |
| cpu_utilization , memory_utilization | usage , threshold , duration , state , time |
| sustained_system_overload | cpu_threshold , packet_loss_threshold , overload_threshold , sustained_overload_counter |
| ipsec_tunnel_up / _down | tunname , time |
| login_success | user , ip , time |
| wifi_client_state_change | mac , ssid , radio , state |
| data_cap_threshold | nested info with percentage , capMB , cycle , cycle_str , interface , profilename , hostname , description |
| thermal_exceeds_limit | limit , current , modem |
| wan_service_type | from , to , interface_name |
 For the full per-type detail, expand the "Example payload for …" blocks in the legacy Alert Push documentation — 26 alert types have worked examples there.

 For reference, the `/alerts/` **API** endpoint returns a different shape: no `data`/`meta` wrapper, no `router_details`, and `account` and `router` as URLs rather than integers. Do not use it as a model for your webhook handler.

## Monitor your own webhooks

Two alert types report on delivery health. Subscribe to them so NCM tells you when webhooks break, rather than discovering a silent gap in your data:

| Identifier | Alert |
| --- | --- |
| http_push_failure | Webhook Failure |
| http_destination_suspended | Webhook Suspended |

```json
{
  "schedule": "hourly",
  "filter_criteria": {
    "alert_type__in": ["http_push_failure", "http_destination_suspended"]
  },
  "http_destinations": ["A_DIFFERENT_DESTINATION_ID"]
}
```

Point these at a **different** destination from the one being monitored — a suspended destination cannot tell you it is suspended.

## Outbound IP addresses

Push notifications are sent over **HTTPS POST** from these NAT gateway addresses. Allowlisting them ensures you accept pushes only from NCM.

| Region | Addresses |
| --- | --- |
| us0 | 54.68.111.210 , 35.167.132.236 , 54.71.14.40 |
| us2 | 3.221.235.88 , 23.23.36.130 , 44.194.143.163 |
| eu4 | 18.198.46.227 , 18.195.131.70 , 3.67.129.67 |
| au5 | 3.104.136.156 , 13.238.12.132 , 13.55.130.228 |
 These are infrastructure addresses and can change; the Enterprise Wireless documentation portal is authoritative.

## Alert type reference

**111 alert types** (119 identifiers, counting the `intel_` forms). Use the identifier in `filter_criteria.alert_type__in`.

 Eight alerts are listed with a second identifier prefixed `intel_` (for example `intel_ethernet_wan_connected`); both forms appear in the source table.

 **For fleet uptime monitoring — the polling replacement in the hero above — use `connection_state`.** A device must be online or offline for 60 seconds before it fires, which debounces flapping links.

 Groupings below are editorial; the source table is a flat list.

#### Connection and WAN state

| Identifier | Alert | Description |
| --- | --- | --- |
| connection_state | NCM Connection State | Occurs when the device loses or regains its connection to NetCloud Manager. A device must be online or offline for 60 seconds before this alert is generated.For more information, see Details on NetCloud Manager Connection State Online and Offline Alerts. |
| wan_status_change | WAN Status Change | Router WAN has plugged, unplugged, connected or disconnected. (Deprecated) |
| wan_service_type | WAN Service Type | Indicates that a WAN device has changed its service type, such as switching from 3G to 4G. Possible service types include: DHCP, LTE, HSPA+, etc. |
| failover_event | Failover Event | A router (or group of routers) has changed the primary WAN interface from a higher- priority interface to a lower-priority interface. |
| ipverify_event | IP Verify Test Result | Occurs when an IP Verify test is complete. |
| ethernet_wan_connected | Ethernet WAN Connected | Indicates that an Ethernet WAN device is now active. Also listed as intel_ethernet_wan_connected . |
| ethernet_wan_disconnected | Ethernet WAN Disconnected | Indicates that an Ethernet WAN device is no longer active. Also listed as intel_ethernet_wan_disconnected . |
| ethernet_wan_plugged | Ethernet WAN Plugged In | An Ethernet WAN device is now attached. |
| ethernet_wan_unplugged | Ethernet WAN Unplugged | An Ethernet WAN device has been removed. |
| ethernet_wan_standby | Ethernet WAN Standby | An Ethernet WAN device is in standby mode |
| modem_wan_connected | Modem WAN Connected | A modem WAN device is now active. Also listed as intel_modem_wan_connected . |
| modem_wan_disconnected | Modem WAN Disconnected | Indicates that a modem WAN device is no longer active. Also listed as intel_modem_wan_disconnected . |
| modem_wan_plugged | Modem WAN Device Plugged In | A modem WAN device is now attached. |
| modem_wan_unplugged | Modem WAN Device Unplugged | A modem WAN device has been removed. |
| modem_wan_standby | Modem WAN Standby | Indicates that a modem WAN device's default connection state is set to Standby. This means the modem is connected to the carrier, but is not sending data. A modem in Standby will failover faster than a modem not in Standby. Standby is enabled by modifying the Default Connection State from the Device UI's Connection Manager table. |

#### Wi-Fi as WAN

| Identifier | Alert | Description |
| --- | --- | --- |
| wwan_connected | WiFi as WAN Connected | Indicates WiFi as WAN is now active. |
| wwan_disconnected | WWAN Disconnected | A WiFi as WAN network is no longer active. |
| wwan_standby | WiFi as WAN Standby | Indicates a WiFi as WAN network is in standby. |
| wwan_network_available | WiFi as WAN Network Available | A WiFi as WAN network is now attached. |
| wwan_network_unavailable | WiFi as WAN Network Unavailable | A WiFi as WAN network has been removed. |

#### Configuration and firmware

| Identifier | Alert | Description |
| --- | --- | --- |
| config_change | Configuration Changed | Occurs when there has been a local configuration change. |
| config_rejected | Configuration Rejected | Occurs when a configuration change was sent to the device but was rejected. |
| config_unacked | Configuration Unacknowledged | Occurs when a configuration change was sent to the device but was not acknowledged by the device. |
| firmware_upgrade | Firmware Upgrade | Occurs when the device's NetCloud OS is upgraded. |
| ncos_firmware_summary_create | NetCloud OS Upgrade Available | Occurs when new NetCloud OS software is available for download. Includes the version number of the new software, the products to which it applies, and a link to the Release Notes. |
| modem_firmware_summary_create | Modem Software Upgrade Available | Occurs when new modem software is available for download. Includes the version number of the new software, the products to which it applies, and a link to the Release Notes. |
| router_group_change | Router Group Change | The specified router has been moved or added to the specified group. Also includes the username associated with the move, the date and time, the group the router was in previously (if applicable) and the group to which the router was added (if applicable). |

#### Security

| Identifier | Alert | Description |
| --- | --- | --- |
| account_locked | Account Locked | The account is locked for 30 minutes after six failed attempts to log into the device. |
| ncm_login_failure | Failed Login Attempt (NCM) | Message template: SOURCE: (source_ip) | USER: (user) | REASON: (reason) |
| login_success | Successful Login (Router) | A login attempt succeeded to the router's Device UI. |
| ip_banned | IP Address Banned | Occurs if the Ban IP Address setting is turned on for a device and someone from a particular IP address attempts and fails to log into the Device UI six times, that IP address is banned for 30 minutes. To enable this setting, login to the Device UI and go to System > Administration > Router Security and click on Advanced Security Mode. Select the Ban IP Address option. |
| ips_activity | Intrusion Activity | Is only relevant for devices with CP Secure Threat Management. Whenever the Threat Management deep packet inspection engine detects an intrusion, that event is recorded in the logs. These events are grouped together for 15 minutes and then reported in NetCloud Manager, so an emailed alert notification might not arrive for approximately 15 minutes after an intrusion. Intrusion Activity alerts include the intrusion details and the action taken by the engine (e.g., Blocked). To edit Threat Management settings, login to the Device UI and select Security > Threat Management. For more information, see CP Secure Threat Management Overview. |
| ips_eng_failure | IPS Engine Failure | For devices using Cradlepoint Secure Threat Management (CPSTM), this alert occurs in the unlikely event that the Threat Management engine fails. You can set the router to either allow or deny traffic with a failure. To edit this setting, log in to the Device UI and go to Security > Threat Management. |
| ips_enable_disable | IPS Enablement Change | Occurs when Intrusion Prevention Systems / Intrusion Detection Systems (IPS/IDS) has been enabled or disabled using Cradlepoint Secure Threat Management (CPSTM) and provides details about who made the change and when it occurred. |
| ips_mode_change | IPS Mode Change | Occurs when the IPS/IDS mode has been changed (for example, from Detect and Prevent to Detect Only) using CPSTM and provides details on what changed, who made the change, and when it occurred. |
| duplicate_ssid | Rogue Access Point Detected | Occurs after running a Wi-Fi site survey when a rogue access point not marked as "known" is detected broadcasting the same SSID as the device running the site survey. This helps identify potential access-point hijacking, evil twin, and man-in-the-middle Wi-Fi attacks. |
| unrecognized_mac_alert | Unrecognized Client | Indicates a client with an unrecognized MAC address has attempted to connect to the device. MAC logging must be enabled to trigger this alert. To enable MAC logging, login to the Device UI and go to Networking > Local Networks> MAC Filter & Logging and select Enable MAC Logging. |
| scep_enrollment_success | SCEP Enrollment Success | This alert is generated when a device successfully enrolls with a SCEP server. |
| scep_enrollment_failure | SCEP Enrollment Failure | This alert is generated when a device attempts to enroll with a SCEP server but can't enroll successfully. |
| span_enabled | SPAN Enabled | SPAN Packet Cloning Enabled |

#### VPN and tunnels

| Identifier | Alert | Description |
| --- | --- | --- |
| ipsec_tunnel_up | IPSec VPN Tunnel Up | Occurs when an IPSec tunnel initially connects. |
| ipsec_tunnel_down | IPSec VPN Tunnel Down | Occurs when a connected IPSec tunnel is disconnected. |
| ipsec_tunnel_restored | IPSec VPN Tunnel Restored | Occurs when a connected IPSec tunnel reconnects after being disconnected. |
| tunnel_limit | Tunnel Limit Reached | Occurs when the number of IPSec tunnel connections has met or exceeded either 90% or 100% of the maximum tunnel limit. |
| zs_tls_tunnel | Zscaler TLS Tunnel State | Indicates the state of a Zscaler tunnel has changed. |
| msft_iot_disconnect | Microsoft IoT Disconnect | Occurs when a Microsoft IoT device disconnects. |

#### Hardware and system health

| Identifier | Alert | Description |
| --- | --- | --- |
| reboot_status_change | Reboot | Occurs when the device is rebooted. Also listed as intel_reboot_status_change . |
| system_error | System Error Requires Reboot | The router rebooted due to a system error that requires a reboot to resolve. |
| sustained_system_overload | Sustained System Overload | The system experienced sustained levels for CPU or Packet Loss that exceeded thresholds. |
| cpu_utilization | CPU Utilization | CPU Utilization crossed a threshold. |
| memory_utilization | Memory Utilization | Memory Utilization crossed a threshold. |
| thermal_exceeds_limit | Temperature Limit Exceeded | Occurs on devices with an internal temperature sensor (COR IBR1100 and IBR1150) when a user-defined temperature limit is reached. To set the temperature limits for the COR IBR1100 Series, login to the Device UI, select System > Administration, and then click the Temperature tab. |
| adc_event | ADC Voltage Event | Occurs on devices with voltage-sensing systems when a voltage limit is reached, and also when the voltage returns to within the accepted range. |
| gpio_state_change | GPIO State Change | Occurs when a device's GPIO pin has changed state. To view or update the GPIO configuration, open the Device UI and select the System > GPIOs tab. This alert requires NetCloud OS 6.0.2 and newer, and a Cradlepoint device with GPIO. |
| sim_door_event | SIM Door Event | Occurs when the SIM door opens or closes. |
| custom_alert | Custom Alert | Is an alert generated by custom code in an NCOS SDK app. |

#### Battery (E100 / E400 / X20)

| Identifier | Alert | Description |
| --- | --- | --- |
| batt_health_alert | Battery Health | Creates an alert when the remaining battery life reaches 62%. |
| batt_over_current_alert | Battery Over Current | Creates an alert when the battery's current reaches 3.2 amps (3200 mA). |
| batt_over_temp_alert | Battery Over Temperature | Creates an alert when the battery's temperature reaches 50 degrees Celsius. |
| batt_over_voltage_alert | Battery Over Voltage | Creates an alert when the battery's recommended voltage reaches 8.5V (8500 mV). |
| batt_sys_low_power_alert | Battery System Low Power | Creates an alert when the battery's power reaches 20% charge. |
| batt_sys_power_off_alert | Battery System Power Off | Creates an alert when the battery's power reaches 7% charge. |
| batt_backup_power_engaged | Battery Backup Power Engaged | When an E400/X20 device loses wall power and switches over to battery backup. |
| batt_maintenance_cycle_started | Battery Maintenance Cycle Started | The battery automatically begins discharging raining its charge then recharges to full power, for optimizing battery life. (E400/X20 devices) |
| batt_maintenance_cycle_ended | Battery Maintenance Cycle Ended | The battery has finished its maintenance cycle. (E400/X20 devices) |
| wall_power_restored | Wall Power Restored | When an E400/X20 device regains wall power and switches back from battery backup mode. |

#### Carrier data and subscriptions

| Identifier | Alert | Description |
| --- | --- | --- |
| data_cap_threshold | Data Cap Threshold | Occurs when a data cap threshold is reached. |
| pdp_account_overage | Pooled Carrier Data Usage | Creates an alert when a user-defined usage percentage is met for a pooled data plan. |
| pdp_router_overage | Carrier Data Usage Per Router | Creates an alert when a user-defined usage percentage is met for a single router. |
| acs_expires_in_180_days | Subscriptions Expiring 180 Days | Indicates account has subscriptions expiring in 180 days.For more information on notification limitations with Subscriptions Expiring alerts, see Alerts Not Supported for Use in Webhooks. Note the potential issues when using this alert. |
| acs_expires_in_90_days | Subscriptions Expiring 90 Days | Indicates account has subscriptions expiring in 90 days |
| acs_expires_in_60_days | Subscriptions Expiring 60 Days | Indicates account has subscriptions expiring in 60 days |
| acs_expires_in_30_days | Subscriptions Expiring 30 Days | Indicates account has subscriptions expiring in 30 days |
| acs_expires_in_15_days | Subscriptions Expiring 15 Days | Indicates account has subscriptions expiring in 15 days |
| acs_expires_in_5_days | Subscriptions Expiring 5 Days | Indicates account has subscriptions expiring in 5 days |
| acs_unlicensed_devices | Unlicensed Devices | Indicates account has new unlicensed devices. |
| acs_unlicensed_in_15_days | Devices Unlicensed in 15 Days | Indicates account has devices unlicensing in 15 days. |
| acs_unlicensed_in_5_days | Devices Unlicensed in 5 Days | Indicates account has devices unlicensing in 5 days. |
| acs_noncompliant_devices | Non-Compliant Devices | Indicates account has new non-compliant devices. |

#### Carrier Selection Intelligence (CSI)

| Identifier | Alert | Description |
| --- | --- | --- |
| csi_test_started | Test Started | All preconditions have been met for CSI (Carrier Selection Intelligence) to start. |
| csi_test_passed | Test Passed | A carrier met or exceeded the benchmarks for a speed test. |
| csi_test_failed | Test Failed | A carrier failed to meet the benchmarks for a speed test. |
| csi_test_error | Test Error | A speed test encountered an error. |

#### Location services

| Identifier | Alert | Description |
| --- | --- | --- |
| geofence_proximity_change | Geo fence Proximity Change | Occurs whenever the device enters or exits the specified Geo-fence |
| device_location_unknown | Device Location Unknown | Occurs when no location is reported for 24 hours on a device with Location Services (GPS) enabled. If a manual location is used the alert is not generated. |
| location_service_change | Location Service Mode Changed | Alert sent when there is change to the configuration of Location Services (including but limited to turning it on/off). This alert contains the time and date of the change, what was changed, and the device or group to which it was applied. |

#### Private cellular (PCN)

| Identifier | Alert | Description |
| --- | --- | --- |
| pcn_ap_connection_state | AP/Core Connection State | The cellular AP specified service type connectivity to the packet core entered the specified state. |
| pcn_ap_radio_status | Cellular Radio Status | The cellular AP radio interface has entered the specified state. |
| pcn_ap_ntp_syn_failure | NTP Sync Issue | The cellular AP cannot reach the NTP server and may have incorrect system time. |
| pcn_ap_ipsec_setup_failure | IPsec Tunnel Setup Failure | The cellular AP failed to setup an IPsec tunnel with the specified packet core. |
| pcn_core_ntp_sync_failure | NTP Sync Issue | The packet core cannot reach the NTP server and may have incorrect system time. |
| pcn_core_port_status_changed | Interface Status | The packet core specified interface has entered the specified state. |
| pcn_core_ipsec_connection_status_changed | IPsec Connection State | The packet core IPsec connectivity to the specified cellular AP entered the specified state. |
| pcn_core_s1_connection_state | S1 Connection State | The packet core S1 connectivity to the specified cellular AP entered the specified state. |
| pcn_core_s1_setup_failure | S1 Setup Failure | The packet core failed to setup an S1 connection with the specfiied cellular AP. |
| pcn_core_ue_dedicated_bearer_alloc_failure | GBR Bearer Setup Failure | The device with the specified IMSI failed to connect to its requested QCI because the guaranteed bitrate requirement could not be met. |
| pcn_sas_grant_success | SAS Grant Success | The cellular AP received a CBRS SAS grant successfully. |
| pcn_sas_grant_expired | SAS Grant Expired | A CBRS SAS Grant has expired for the cellular AP. |
| pcn_sas_grant_suspended | SAS Grant Suspended | A CBRS SAS Grant was suspended for the cellular AP. |
| pcn_sas_grant_terminated | SAS Grant Terminated | A CBRS SAS Grant was terminated for the cellular AP. |
| pcn_sas_no_response | No SAS Response | The cellular AP did not receive a response from the CBRS SAS server. |

#### LAN clients

| Identifier | Alert | Description |
| --- | --- | --- |
| wifi_client_state_change | WiFi Client State Changed | Occurs when a WiFi client changes state. For example, when a client's state changes from connected to disconnected (requires NetCloud OS version 6.6.1 and newer). |

#### NetCloud OS apps

| Identifier | Alert | Description |
| --- | --- | --- |
| router_sdk_app_execution_state_changed | NCOS App Execution State Changed | Indicates that an NCOS SDK app that is running on a group has entered a different execution state (start, stop, error, etc). |
| router_sdk_mismatched_app_action | Unexpected NCOS App Installed | Indicates that an unexpected NCOS SDK app is found installed, an expected NCOS SDK app is unexpectedly uninstalled, or a that an NCOS app unknown to the system is found installed. |

#### Remote Connect

| Identifier | Alert | Description |
| --- | --- | --- |
| remote_connection_request | Remote Connect Started | A remote connect session to the specified router has been opened. |
| remote_connection_destroyed | Remote Connect Ended | A remote connect session to the specified router has been closed. |

#### VRRP

| Identifier | Alert | Description |
| --- | --- | --- |
| vrrp_new_master | VRRP State Changed to Master | The specified router has been become a VRRP master. You can further configure this alert to only be sent if the router was previously a backup or in a fault state for a specified period. Also listed as intel_vrrp_new_master . |
| vrrp_new_backup | VRRP State Changed to Backup | The specified router has become a VRRP backup. You can further configure this alert to only be sent if the router was previously a master or in a fault state for a specified period. Also listed as intel_vrrp_new_backup . |
| vrrp_new_fault | VRRP State Changed to Fault | The specified router is in a VRRP fault state. You can further configure this alert to only be sent if the router was previously a backup or a master for a specified period. Also listed as intel_vrrp_new_fault . |

#### Webhook health

| Identifier | Alert | Description |
| --- | --- | --- |
| http_push_failure | Webhook Failure | Webhook delivery failed for alert with UUID (created_uuid), type (type), account (account_id), router (router_id), at time (created_ts) with message: (message). |
| http_destination_suspended | Webhook Suspended | Previously configured webhook is in a suspended state. |

## Integration examples

Both assume the signature has already been verified as in Step 4.

**ServiceNow**

### ServiceNow — open an incident when a device goes offline

```python
import requests

def handle_ncm_webhook(payload):
    for record in payload.get("data", []):
        if record.get("type") != "connection_state":
            continue
        if (record.get("info") or {}).get("state") != "offline":
            continue          # skip the recovery event
        details = record.get("router_details") or {}
        requests.post(
            "https://your-instance.service-now.com/api/now/table/incident",
            json={
                "short_description": record.get("friendly_info", "NCM alert"),
                "urgency": "2",
                "category": "Network",
                "cmdb_ci": details.get("serial_number", ""),
                "u_device_name": details.get("name", ""),
            },
            auth=(SN_USER, SN_TOKEN),
        )
```

`connection_state` fires in both directions, so filter on `info.state` or you will open a ticket when the device comes back. `router_details` gives you the serial number and name for CMDB correlation.

**Slack**

### Slack — post on any alert

```python
import requests

SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"

def handle_ncm_webhook(payload):
    for record in payload.get("data", []):
        details = record.get("router_details") or {}
        requests.post(SLACK_WEBHOOK, json={
            "text": f"🚨 NCM *{record.get('type', 'unknown')}* on "
                    f"*{details.get('name', 'unknown device')}* — "
                    f"{record.get('friendly_info', 'no detail')}"
        })
```

## API reference

Configured through these **v2** endpoints:

| Endpoint | Purpose |
| --- | --- |
| alert_push_destinations | Create and manage HTTP destinations |
| test_alert_push_destinations | Test that NCM can reach a destination |
| alert_rules | Configure which events trigger notifications |
| alerts | View generated alerts |
| router_alerts | View device-specific alerts |
 Authentication uses all four v2 key headers: `X-ECM-API-ID`, `X-ECM-API-KEY`, `X-CP-API-ID`, `X-CP-API-KEY`.
