Signal Configuration Guide
This guide walks through configuring signal rules for anomaly detection in SpatialFlow. For background on what signals are and how they work, see the Signals concept page.
Dwell Detection Configuration
Dwell detection fires when a device stays inside a geofence beyond a time threshold. Thresholds are configured per-workflow in the trigger configuration, so multiple workflows can monitor the same geofence with different sensitivity levels.
Setting Up a Dwell Workflow
Create a workflow with a geofence_dwell trigger type:
curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Warehouse Dwell Alert",
"description": "Alert when a vehicle dwells at warehouse for 5+ minutes",
"trigger_config": {
"type": "geofence_dwell",
"event_type": "geofence_dwell",
"geofence_ids": ["a1b2c3d4-5678-90ab-cdef-1234567890ab"],
"dwell_threshold": 300
},
"steps": [
{
"type": "slack",
"name": "Notify warehouse team",
"config": {
"integration_id": "int_slack123",
"channel": "#warehouse-ops",
"message": "Vehicle {{trigger.device_id}} has been at {{trigger.geofence_name}} for {{trigger.dwell_time}}s"
}
}
],
"is_active": true
}'
Dwell Threshold
The dwell_threshold field specifies the minimum dwell duration in seconds before the signal fires. Valid range is 30 to 86,400 seconds (30 seconds to 24 hours). If omitted, the default is 300 seconds (5 minutes).
The threshold is per-workflow, meaning you can have multiple workflows monitoring the same geofence with different thresholds. Each workflow fires independently when its threshold is crossed.
Sensitivity Tuning
Lower thresholds detect shorter dwell events (more sensitive, more alerts). Higher thresholds require longer presence before firing (less sensitive, fewer alerts).
| Scenario | Threshold | Notes |
|---|---|---|
| Quick stop detection | 60s (1 min) | Delivery confirmation |
| Standard dwell | 300s (5 min) | Default, general purpose |
| Extended stay | 1800s (30 min) | Warehouse loading |
| Long-term presence | 3600s (1 hour) | Job site monitoring |
Dwell thresholds are cached with a 60-second TTL (THRESHOLD_CACHE_TTL = 60). After changing a workflow's dwell threshold, it may take up to 60 seconds for the new value to take effect.
Policy Violation Configuration
Policies are workspace-level rules that evaluate on every processed location update. When a device's location matches a policy's conditions, a policy_violation signal fires.
Policy Structure
Each policy combines:
- Geofence condition -- The spatial boundary to monitor (required)
- Time window -- When the policy is active (optional; null = always active)
- Device filter -- Which devices to evaluate (optional; null = all devices)
- Role filter -- Which user roles to evaluate (optional; null = all roles)
All conditions must match simultaneously for a violation to fire.
Time Windows
Time windows restrict when a policy is active. Use them for after-hours monitoring, weekend restrictions, or shift-based rules.
{
"days_of_week": [0, 1, 2, 3, 4],
"start_time": "22:00",
"end_time": "06:00",
"timezone": "America/New_York"
}
Field details:
days_of_week-- Uses Python weekday convention: 0 = Monday, 1 = Tuesday, ..., 6 = Sundaystart_time/end_time-- 24-hour format (HH:MM). Overnight spans are supported (e.g., 22:00 to 06:00)timezone-- IANA timezone string (e.g.,America/New_York,Europe/London,UTC)
Set time_window to null for policies that should be active at all times.
Device Filters
Device filters narrow which devices the policy applies to, based on device type and metadata.
{
"device_types": ["vehicle", "tracker"],
"metadata_match": {"department": "logistics"}
}
Field details:
device_types-- List of device types to include. Valid values:mobile,vehicle,iot,tracker,othermetadata_match-- Key-value pairs that must match the device's metadata. All specified keys must match (AND logic)
Set device_filter to null for policies that apply to all devices.
Role Filters
Role filters restrict the policy to devices owned by users with specific workspace roles.
Valid roles: field_worker, member, manager, owner
["field_worker", "member"]
Set role_filter to null for policies that apply regardless of the device owner's role.
Policy violations use two TTL values for lifecycle management:
- VIOLATION_TTL = 86,400s (24 hours) -- Active violations are automatically cleaned up after 24 hours
- DEDUPE_TTL = 300s (5 minutes) -- After a violation is resolved, the same policy cannot re-fire for 5 minutes, preventing rapid re-triggers when a device hovers at a geofence boundary
Workflow Trigger Configuration
Signals trigger workflows through two trigger types. Choose based on your detection needs.
Geofence Dwell Trigger
Use type: "geofence_dwell" for dwell detection. This is the primary path for time-based anomalies.
Schema:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | yes | -- | Must be "geofence_dwell" |
event_type | string | yes | -- | Must be "geofence_dwell" |
geofence_ids | list | no | [] (all) | Limit to specific geofences |
group_ids | list | no | [] | Include geofences from groups |
dwell_threshold | number | no | 300 | Seconds before dwell fires (30-86400) |
device_filters | object | no | {} | Filter by device criteria |
Signal Trigger
Use type: "signal" for policy violations and general-purpose signal matching.
Schema:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | yes | -- | Must be "signal" |
signal_types | list | yes | -- | Signal types to match (e.g., ["policy_violation"]) |
signal_states | list | no | ["confirmed"] | States to trigger on |
geofence_ids | list | no | [] | Limit to specific geofences |
device_filters | object | no | {} | Filter by device criteria |
Template Variables
Signal triggers provide these template variables for use in action configurations:
| Variable | Description |
|---|---|
{{trigger.signal_type}} | Signal type (e.g., dwell, policy_violation) |
{{trigger.state}} | Signal state (e.g., confirmed, ended) |
{{trigger.device.id}} | Device identifier |
{{trigger.device.name}} | Device name |
{{trigger.geofence.name}} | Geofence name (if applicable) |
{{trigger.explanation}} | Structured explanation object |
{{trigger.reason_codes}} | List of machine-readable reason codes |
{{trigger.metadata}} | Signal metadata (location, thresholds, duration) |
Common Patterns
After-Hours Access Alert
Detect when vehicles enter a restricted area outside business hours. Combines a policy violation with a Slack notification.
Policy configuration:
{
"name": "After-hours warehouse access",
"geofence_id": "warehouse-geofence-uuid",
"time_window": {
"days_of_week": [0, 1, 2, 3, 4],
"start_time": "18:00",
"end_time": "07:00",
"timezone": "America/Chicago"
},
"device_filter": {
"device_types": ["vehicle"]
},
"role_filter": null
}
Workflow trigger:
{
"type": "signal",
"signal_types": ["policy_violation"],
"signal_states": ["confirmed"],
"geofence_ids": ["warehouse-geofence-uuid"]
}
Extended Dwell Alert
Alert warehouse operations when a vehicle stays at a loading dock for more than 30 minutes.
{
"name": "Extended Loading Dock Dwell",
"trigger_config": {
"type": "geofence_dwell",
"event_type": "geofence_dwell",
"geofence_ids": ["loading-dock-uuid"],
"dwell_threshold": 1800
},
"steps": [
{
"type": "email",
"name": "Alert operations manager",
"config": {
"integration_id": "int_email456",
"to": ["ops-manager@example.com"],
"subject": "Extended dwell at loading dock",
"body_text": "Vehicle {{trigger.device_id}} has been at {{trigger.geofence_name}} for over 30 minutes ({{trigger.dwell_time}}s)."
}
}
]
}
Multi-Threshold Dwell
Monitor the same geofence with two sensitivity levels by creating two workflows with different dwell_threshold values:
- Workflow 1 --
dwell_threshold: 300(5 min) triggers an informational Slack message to#field-updates - Workflow 2 --
dwell_threshold: 1800(30 min) triggers an urgent Slack message to#dispatch-alerts
Both workflows use type: "geofence_dwell" with the same geofence_ids. Each fires independently when its threshold is crossed.
Troubleshooting
Signal Not Firing
- Check GPS jitter filters: Location updates with accuracy > 100 meters (
MAX_ACCURACY_METERS) are dropped. Verify the device is sending high-quality GPS fixes. - Check minimum distance: Movements under 25 meters (
MIN_DISTANCE_METERS) are filtered out. Stationary devices with GPS drift may not generate new containment checks. - Verify device is active: Inactive devices do not process location updates.
- Verify workflow is active: Draft or paused workflows do not trigger.
- Check signal_types in trigger config: Ensure the workflow's trigger config includes the correct signal type.
Too Many Signals
- Increase dwell threshold: Higher thresholds require longer presence, reducing alert volume.
- Check cooldown period: Each device x geofence x event type combination has a 300-second cooldown (
COOLDOWN_SECONDS). Signals cannot re-fire within this window. - Review policy time windows: Ensure policies are restricted to the intended hours. A null time window means the policy is always active.
- Check policy deduplication: The 5-minute dedupe TTL (
DEDUPE_TTL = 300) prevents rapid re-triggers after resolution.
Signal Fires but Workflow Does Not Execute
- Check
signal_statesin trigger config: The default is["confirmed"], not"started". If your signal is in thestartedstate, the workflow will not trigger unless you explicitly include"started"in the states list. - Check
signal_typesmatch: The workflow'ssignal_typesmust include the specific signal type being generated. - Verify geofence filter: If
geofence_idsis set in the trigger config, the signal's geofence must be in that list.
Next Steps
- Signals -- Understand signal types, lifecycle, and explainability
- Workflows -- Build automation pipelines triggered by signals
- Geofences -- Create the spatial boundaries that signals monitor
- API Reference -- Complete API documentation