Skip to main content

Location Update

The Location Update trigger runs a workflow once for every location fix a device reports that SpatialFlow accepts as the device's newest position. Its main use is forwarding each fix to another system with the Webhook action.

Every other trigger waits for something to happen: a geofence crossing, a schedule, a device going quiet. This one has no condition. If you only need to know when a device enters a place or stops moving, use one of those instead.

When It Fires​

A fix starts the workflow when SpatialFlow stores it as the device's latest position. It does not fire for:

  • A fix older than the device's last accepted position, such as an offline batch that uploads late. The track shows the points, but no workflow runs for them.
  • A duplicate of a fix already accepted.
  • A fix recorded while the device's shift was paused.
  • A point added by a CSV or GPX location import. Only live reporting from the device fires it.

Each workflow runs at most once per fix, so a retried upload doesn't run it twice.

Configuration​

KnobTypeDefaultDescription
device_filtersobject{} (all devices)Optional filter: device_ids, exclude_device_ids, device_types (mobile, vehicle, iot, tracker, other), metadata_match.
{
"type": "location_update",
"device_filters": { "device_types": ["vehicle"] }
}
This trigger runs often

A device that reports every 20 seconds starts 3 workflow runs a minute, per matching workflow. Fleets multiply that quickly. Use device_filters to match only the devices you need, and keep the actions cheap.

Template Variables​

Reference these in any action with the standard {{trigger.xxx}} syntax. Every variable is always present; a value the device didn't report is null.

VariableValue
{{trigger.event_type}}"location_update"
{{trigger.device_id}}External device identifier
{{trigger.device_name}}Human-readable device name
{{trigger.timestamp}}When the fix was recorded, ISO 8601 with a +00:00 offset
{{trigger.timestamp_ms}}The same instant as integer epoch milliseconds
{{trigger.location.latitude}}Latitude in decimal degrees
{{trigger.location.longitude}}Longitude in decimal degrees
{{trigger.accuracy_m}}Horizontal accuracy in meters
{{trigger.speed_mps}}Speed in meters per second
{{trigger.heading_deg}}Heading in degrees clockwise from north
{{trigger.altitude_m}}Altitude in meters
{{trigger.battery_level}}Battery level in percent
{{trigger.battery_charging}}true or false
{{trigger.reason}}"heartbeat", "stationary", or null when the device moved normally

An iPhone can send its stop report with the same timestamp as the fix just before it. That report runs the workflow a second time for that timestamp, with reason set to "stationary". A receiver that deduplicates on device and timestamp would drop that report, so build its key from {{execution.id}} instead. Adding {{trigger.reason}} to the key also tells the two runs apart, but a normal fix has no reason, and the placeholder then renders as the literal text {{trigger.reason}}.

Example: Forward Each Fix to Another System​

Scenario. You keep a map of vehicle positions in another system and want every fix posted to it as a GeoJSON feature.

Workflow.

  1. Trigger: Location Update with device_types: ["vehicle"].
  2. Action: Webhook, POST to your endpoint.
{
"type": "webhook",
"name": "Post fix as a feature",
"config": {
"url": "https://example.com/api/features",
"method": "POST",
"headers": { "Content-Type": "application/json" },
"body": "{\"geometry\":{\"type\":\"Point\",\"coordinates\":[{{trigger.location.longitude}},{{trigger.location.latitude}}]},\"properties\":{\"device_name\":\"{{trigger.device_name}}\",\"recorded_at\":\"{{trigger.timestamp}}\",\"speed_mps\":{{trigger.speed_mps}}}}"
}
}

If the receiving system wants an X-Api-Key header, don't put it in the step. A step can't store credentials, so create a Webhook integration with the api_key auth type and pick it as the action's destination. See Webhook (HTTP Request).

A template outside quotes, like {{trigger.speed_mps}} above, renders as a JSON value: a number as a number, and a missing value as null. The body stays valid JSON when the device reported no speed. Wrap a template in quotes to get a string, as the example does for device_name and recorded_at. This works inside a JSON object or list; the builder won't save a JSON body that is nothing but a template.

Idempotency keys and restricted characters​

{{trigger.timestamp}} contains : and +, which a receiver may reject in a header or URL. Use {{trigger.timestamp_ms}} there: it is the same instant as integer epoch milliseconds, digits only, and a bare {{trigger.timestamp_ms}} in a JSON body is a number. This fix's {{trigger.timestamp}} is an ISO 8601 string; the aggregate geofence trigger publishes Unix seconds instead, and {{trigger.timestamp_ms}} is integer milliseconds on every trigger that has a timestamp.

{{execution.id}} is unique per run. The automatic retries of a webhook delivery (a network error, a 5xx or a 429) happen inside the same run, so they send the same {{execution.id}} and a key built from it deduplicates them. A separate run gets a new id, even for the same fix. If the receiver must treat separate runs of the same fix as duplicates, build the key from trigger fields, for example an Idempotency-Key header of {{trigger.device_id}}:{{trigger.timestamp_ms}}.

See Also​