Skip to main content

PagerDuty Alerting

Open PagerDuty incidents directly from SpatialFlow workflows using the Events API v2. This recipe wires any of the live-ops triggers (Device Offline, Device Stuck, Shift Overdue) into your existing PagerDuty escalation policy.

Prerequisites​

  • A PagerDuty account with permission to add integrations to a service
  • A SpatialFlow workflow with one of the three live-ops triggers configured
  • An API key or JWT token for the SpatialFlow API (see Authentication)

Step 1 — Get an Events API v2 Integration Key​

  1. In PagerDuty, navigate to Services → choose the destination service (or create one)
  2. Open the Integrations tab and click Add an integration
  3. Select Events API V2 and click Add
  4. Expand the new integration row and copy the Integration Key — it's a 32-character hex string that PagerDuty also calls a "routing key"
Treat the integration key as a secret

Anyone with this key can open an incident on your service. Store it in your workspace's Integrations settings rather than hard-coding it.

Step 2 — Send Events Through a Webhook Integration​

The routing key is a credential, and a workflow step can't store credentials, so the key lives in a Webhook integration along with the event fields every alert shares. Each workflow step links that integration and adds the fields for its own trigger.

Create the integration​

  1. Open Integrations, find Webhook, and click Connect.
  2. Set URL to https://events.pagerduty.com/v2/enqueue and HTTP Method to POST. Leave Headers empty: SpatialFlow sends Content-Type: application/json on its own.
  3. Paste this into Body, replace YOUR_INTEGRATION_KEY with the key from Step 1, and click Create Integration:
{
"routing_key": "YOUR_INTEGRATION_KEY",
"event_action": "trigger",
"payload": {
"summary": "SpatialFlow alert",
"source": "spatialflow",
"severity": "warning"
}
}
  1. Click Manage on the Webhook card, then Test Connection. The test sends this body to PagerDuty, which opens a "SpatialFlow alert" incident on your service. A 2xx reply marks the integration verified. A test has no trigger event, so if you put a placeholder such as {{trigger.device_name}} in the integration's body, the test incident shows it as written.
  2. Resolve that test incident in PagerDuty.

A workflow can only link a verified integration. Saving the integration again clears its verification, so run Test Connection after every edit. When you edit it, the body shows "routing_key": "[REDACTED]". Leave that line as it is to keep the saved key, or replace the placeholder with a new key.

Already using a Webhook integration?

Once the workspace has an active Webhook integration, the card shows Manage instead of Connect, and Manage edits the existing integration. To add the PagerDuty integration beside it, create it with the create integration endpoint: "type": "webhook", with the URL, method and body above in config. Then open it with Manage and run Test Connection.

In the Workflow Builder, drag a Webhook action onto the canvas after your trigger node and connect them. In its properties, set Destination to Integration and pick the integration under Webhook Integration. Then paste the body for your trigger from the matching section below into Body (JSON).

The step's body is merged into the integration's, and its fields win: the step supplies dedup_key and the trigger's payload fields, and routing_key and event_action come from the integration. A step with an empty body sends the integration's body as it is. Keep the routing key out of the step: a step that holds one can't be saved.

Step body — Device Offline​

{
"dedup_key": "spatialflow-device-offline-{{trigger.device_id}}",
"payload": {
"summary": "Device offline: {{trigger.device_name}} silent for {{trigger.minutes_since_seen}} min",
"source": "{{trigger.device_id}}",
"severity": "warning",
"component": "{{trigger.device_type}}",
"group": "fleet",
"class": "device_last_seen",
"custom_details": {
"device_id": "{{trigger.device_id}}",
"device_name": "{{trigger.device_name}}",
"device_type": "{{trigger.device_type}}",
"last_seen_at": "{{trigger.last_seen_at}}",
"minutes_since_seen": "{{trigger.minutes_since_seen}}",
"threshold_minutes": "{{trigger.threshold_minutes}}",
"latitude": "{{trigger.location.latitude}}",
"longitude": "{{trigger.location.longitude}}",
"workspace_id": "{{trigger.workspace_id}}"
}
}
}

Step body — Device Stuck​

{
"dedup_key": "spatialflow-device-stuck-{{trigger.device_id}}",
"payload": {
"summary": "Device stuck: {{trigger.device_name}} moved {{trigger.meters_moved_in_window}} m in {{trigger.window_minutes}} min",
"source": "{{trigger.device_id}}",
"severity": "warning",
"component": "{{trigger.device_type}}",
"group": "fleet",
"class": "device_stuck",
"custom_details": {
"device_id": "{{trigger.device_id}}",
"device_name": "{{trigger.device_name}}",
"device_type": "{{trigger.device_type}}",
"meters_moved_in_window": "{{trigger.meters_moved_in_window}}",
"window_minutes": "{{trigger.window_minutes}}",
"threshold_distance_meters": "{{trigger.threshold_distance_meters}}",
"latitude": "{{trigger.location.latitude}}",
"longitude": "{{trigger.location.longitude}}",
"workspace_id": "{{trigger.workspace_id}}"
}
}
}

Step body — Shift Overdue​

{
"dedup_key": "spatialflow-shift-overdue-{{trigger.device_id}}",
"payload": {
"summary": "Shift overdue: {{trigger.device_name}} has been on shift for {{trigger.hours_elapsed}} hours",
"source": "{{trigger.device_id}}",
"severity": "warning",
"component": "{{trigger.device_type}}",
"group": "shifts",
"class": "shift_overdue",
"custom_details": {
"device_id": "{{trigger.device_id}}",
"device_name": "{{trigger.device_name}}",
"device_type": "{{trigger.device_type}}",
"shift_started_at": "{{trigger.shift_started_at}}",
"hours_elapsed": "{{trigger.hours_elapsed}}",
"threshold_hours": "{{trigger.threshold_hours}}",
"latitude": "{{trigger.location.latitude}}",
"longitude": "{{trigger.location.longitude}}",
"workspace_id": "{{trigger.workspace_id}}"
}
}
}
About dedup_key

The dedup_key ties multiple trigger fires for the same device to the same PagerDuty incident — PD will not open a second incident as long as the first one is still alive. This pairs naturally with repeat_policy: every_interval on the SpatialFlow side: each repeat fire becomes a follow-up event on the open incident rather than a new page.

If you want a brand-new incident per fire, change the dedup_key to include a timestamp (e.g., "spatialflow-device-offline-{{trigger.device_id}}-{{trigger.last_seen_at}}").

Step 3 — Test the Workflow​

  1. In the Workflow Builder, click Test
  2. Review the mock trigger payload and run the test
  3. In PagerDuty, open the service's incident list — you should see the new incident within a couple of seconds
  4. If the test passes, Save then Deploy the workflow
Confirming via curl

You can verify the integration key independently of SpatialFlow:

curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d '{
"routing_key": "YOUR_INTEGRATION_KEY",
"event_action": "trigger",
"dedup_key": "manual-test-1",
"payload": {
"summary": "Manual smoke test from curl",
"source": "localhost",
"severity": "info"
}
}'

A 200 OK with {"status":"success", ...} confirms the key is valid.

Resolving Incidents​

To automatically close PagerDuty incidents when the device recovers, you'd need a paired resolve event. SpatialFlow does not currently emit a "recovery" trigger when a device starts reporting again or a shift ends — so the typical pattern is:

  • Use repeat_policy: once_until_seen (the default) so the workflow does not re-page during the same anomaly window
  • Have your on-call resolve the incident manually once they've handled it
  • If you need automatic resolution, post-process the SpatialFlow webhook stream on your end and emit your own resolve events against PagerDuty

Best Practices​

  • One service per anomaly class — split Device Offline, Device Stuck, and Shift Overdue across different PagerDuty services so each has its own escalation policy and on-call rotation
  • Severity tuning — warning is a sensible default; reserve critical for production-impacting anomalies (e.g., a regulated hours-of-service breach)
  • Use custom_details — PagerDuty's mobile app and incident summary view both surface custom details prominently, so include latitude/longitude and last_seen_at for fast triage
  • Store the integration key in an Integration rather than per-workflow so you can rotate it in one place

Troubleshooting​

PagerDuty returns Invalid Routing Key. The integration key is wrong, or the integration has been deleted from the service. Regenerate it in PagerDuty (Step 1).

Incidents open but then immediately auto-resolve. Your service has an aggressive auto-resolve timer. Open the service in PagerDuty → Incident Settings → adjust Auto-resolve incidents.

Multiple devices share a single incident. That's a dedup_key collision — the recipe scopes the key by device_id, but if you have a workflow that fires across multiple devices simultaneously and you used a static dedup key, they'll all merge. Always interpolate {{trigger.device_id}} into the dedup key.

See Also​