Skip to main content

TMS/CRM Addresses to Tagged Geofences

SpatialFlow does not require a native connector to your TMS or CRM. The bulk import API and workflows together are the integration story: your TMS exports an address list, you import it via the bulk API, the addresses become tagged geofences, a workflow fires on the shared tag, and a webhook or action delivers the result back to your system.

This recipe walks through each step.

Prerequisites

  • A SpatialFlow workspace with bulk import enabled (Professional tier or above)
  • An address export from your TMS or CRM (CSV or JSON format)
  • An API key with write access to geofences and workflows — generate one from Dashboard > Account > API Keys

Step 1 — Export Addresses from Your TMS or CRM

Most TMS and CRM systems (Onfleet, Salesforce, HubSpot, spreadsheets, custom databases) can export a CSV with at minimum a street address column.

For best results, include:

  • address (required) — full street address including city, state, and ZIP (e.g., 123 Main St, Austin TX 78701). The more complete the address, the more accurate the geocode.
  • name (recommended) — customer name, stop name, or site identifier. This becomes the geofence display name and is used for dedup matching on re-import.
  • tags (optional) — use a consistent tag like tms-sync or fleet-stops-2026-06 to group all geofences from this import together.

The CSV must use the address column header exactly as shown. See the Bulk Import CSV Schema for the full column reference including buffer_meters and tag formatting rules.

Example export

address,name,tags
"1234 Commerce Dr, Memphis TN 38116",Acme Distribution Center,"tms-sync,tier:gold"
"567 Industrial Blvd, Nashville TN 37209",Riverside Depot,"tms-sync"
"890 Airport Connector, Atlanta GA 30320",ATL Cross-Dock,"tms-sync,priority"

Step 2 — Import via the Bulk API

The bulk API is JSON-only — there is no CSV file-upload endpoint. Parse your export locally and send the rows as a JSON items array. Run POST /api/v1/geofences/preview first to geocode and review, then commit with POST /api/v1/geofences/bulk using dedup_strategy=skip so the import is safe to re-run (name-matched rows are skipped). Include the shared tag (e.g., tms-sync) in each item's tags so the resulting geofences can be targeted as a group by a single workflow in Step 3.

cURL example

curl -X POST https://api.spatialflow.io/api/v1/geofences/bulk \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dedup_strategy": "skip",
"items": [
{"index": 0, "address": "1234 Commerce Dr, Memphis TN 38116", "name": "Acme Distribution Center", "status": "ok", "tags": ["tms-sync", "tier:gold"]},
{"index": 1, "address": "567 Industrial Blvd, Nashville TN 37209", "name": "Riverside Depot", "status": "ok", "tags": ["tms-sync"]}
]
}'

The shared tms-sync tag is set per item (there is no global tag field), so every resulting geofence carries it. Each item's status is the outcome returned by the preview step.

After the import completes, the dashboard shows per-row outcome states (success, low-confidence, interpolated, no-match, ambiguous, duplicate). Rows that could not be geocoded appear in a downloadable error CSV for correction and re-import. See the Bulk Import CSV Schema for a full description of each outcome.

For the JSON request shape, SDK examples, and rate limits, see the Bulk Create Geofences API reference.

Step 3 — Create a Workflow Targeting the Tag

Once the geofences are imported, create a workflow that fires whenever any device enters (or exits) a geofence carrying the tms-sync tag. You do not need to list individual geofence IDs — the tag acts as the grouping selector, so new geofences added in future imports are automatically covered.

In the SpatialFlow dashboard:

  1. Navigate to Workflows > New Workflow.
  2. Add a trigger node. Set the trigger type to Geofence Entry (or Exit).
  3. Set the geofence target mode to Tags and select tms-sync (or whichever tag you used in Step 2).
  4. Connect the trigger to an action node (Step 4).

For the full workflow JSON schema and advanced trigger options, see the Workflow Automation guide.

Step 4 — Add a Webhook or Action

Connect the trigger to an action that delivers the event back to your TMS or CRM.

Webhook to your TMS inbound endpoint

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "TMS Sync — Geofence Entry",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"target_mode": "tags",
"geofence_tag_ids": ["11111111-2222-3333-4444-555555555555"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "webhook",
"label": "Notify TMS",
"config": {
"url": "https://your-tms.example.com/api/inbound/arrival",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TMS_API_KEY"
},
"body": {
"device_id": "{{trigger.device_id}}",
"device_name": "{{trigger.device_name}}",
"stop_name": "{{trigger.geofence_name}}",
"arrived_at": "{{trigger.timestamp}}",
"location": {
"lat": "{{trigger.location.latitude}}",
"lng": "{{trigger.location.longitude}}"
}
}
}
}
}
],
"edges": [
{"id": "edge-1", "source": "trigger-1", "target": "action-1"}
]
}'

The workflow fires for every geofence tagged tms-sync, so a single workflow covers your entire imported stop list.

note

In the raw trigger config, geofence_tag_ids holds tag UUIDs, not tag names — passing ["tms-sync"] there is rejected (400). Set target_mode to "tags" and list one or more tag IDs. Look up a tag's UUID with GET /api/v1/geofences/tags (each tag returns its id), or let the dashboard workflow builder resolve the name for you when you pick the tag from the chip picker.

Other available action types include email, SMS, and Slack. For receiver implementation patterns, retry handling, and security (signature verification), see the Webhook Integration guide.

Keeping Geofences in Sync (Idempotent Re-import)

Most TMS/CRM systems update stop lists daily or on dispatch. Re-importing with dedup_strategy=skip is safe and idempotent:

  • Unchanged addresses — skipped; existing geofences are preserved.
  • New addresses — geocoded and added.
  • Removed addresses — not automatically deleted; archive or tag-filter as needed.

To refresh stops whose physical address has changed, edit those geofences individually. Note that dedup_strategy=override does not update an existing geofence in place — it creates a second geofence for the same name, so running it on a recurring sync steadily accumulates duplicates. To guarantee a clean slate (no stale stops), delete geofences carrying the tag first, then re-import a fresh list.


See also: