Skip to main content

Workflow Automation Guide

Build powerful multi-step location-based automations with SpatialFlow workflows. This guide covers common patterns, best practices, and real-world examples.

Overview

Workflows in SpatialFlow automate actions when geofence events occur. Think of workflows as "if-then" rules:

If a device enters/exits a geofence → Then execute one or more actions

Common use cases:

  • Send Slack notification when vehicle enters delivery zone
  • Trigger webhook and send email when unauthorized device enters restricted area
  • Log to database when employee arrives at job site

Quick Start

Basic Workflow Structure

Every workflow has three components:

  1. Name: Descriptive identifier
  2. Trigger node: Geofence event that starts the workflow
  3. Action nodes: Actions to execute, connected by edges

Example:

{
"name": "Office Entry Notification",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "slack",
"label": "Slack Notification",
"config": {
"webhook_url": "https://hooks.slack.com/services/YOUR/WEBHOOK",
"message": "{{trigger.device_name}} arrived at the office"
}
}
}
],
"edges": [{ "id": "edge-1", "source": "trigger-1", "target": "action-1" }]
}

Creating Your First Workflow

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Delivery Zone Entry Alert",
"description": "Notify operations team when driver enters delivery zone",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["your-geofence-id"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "webhook",
"label": "HTTP Request",
"config": {
"url": "https://your-app.com/api/delivery-alerts",
"method": "POST",
"headers": {
"Content-Type": "application/json"
}
}
}
}
],
"edges": [
{"id": "edge-1", "source": "trigger-1", "target": "action-1"}
]
}'

Trigger Types

Geofence Entry

Fires when a device enters a geofence boundary.

{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["b2c3d4e5-f6a7-8901-bcde-f12345678901"]
}
}
}

Use cases:

  • Customer arrival notifications
  • Check-in automation
  • Zone entry logging

Geofence Exit

Fires when a device leaves a geofence boundary.

{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_exit",
"label": "Geofence Exit",
"config": {
"geofence_ids": ["b2c3d4e5-f6a7-8901-bcde-f12345678901"]
}
}
}

Use cases:

  • Departure confirmations
  • Dwell time calculations
  • Zone exit alerts

Available Actions

1. Webhook (HTTP Request)

Send HTTP request to any URL.

{
"id": "action-1",
"type": "action",
"data": {
"actionType": "webhook",
"label": "HTTP Request",
"config": {
"url": "https://your-app.com/webhooks/geofence-event",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"X-Custom-Header": "value"
},
"body": {
"device": "{{trigger.device_name}}",
"geofence": "{{trigger.geofence_name}}",
"event_type": "{{trigger.event_type}}"
}
}
}
}

Template Variables:

  • {{trigger.device_id}}, {{trigger.device_name}}
  • {{trigger.geofence_id}}, {{trigger.geofence_name}}
  • {{trigger.location.latitude}}, {{trigger.location.longitude}}
  • {{trigger.event_type}}, {{trigger.timestamp}}

2. Slack Notification

Send message to Slack channel.

With an incoming webhook, the destination channel is selected in Slack when the webhook is created. Omit channel for webhook delivery. Use channel or default_channel only with a bot-token/OAuth Slack integration that supports channel selection.

{
"id": "action-1",
"type": "action",
"data": {
"actionType": "slack",
"label": "Slack Notification",
"config": {
"webhook_url": "https://hooks.slack.com/services/YOUR/WEBHOOK",
"message": "🚗 {{trigger.device_name}} entered {{trigger.geofence_name}}"
}
}
}

3. Email (SES/SendGrid)

Send email notification.

{
"id": "action-1",
"type": "action",
"data": {
"actionType": "email",
"label": "Email Notification",
"config": {
"to": "operations@example.com",
"subject": "Geofence Alert: {{trigger.geofence_name}}",
"body": "Device {{trigger.device_name}} entered {{trigger.geofence_name}} at {{trigger.timestamp}}"
}
}
}

4. SMS (Twilio)

Send SMS alert.

{
"id": "action-1",
"type": "action",
"data": {
"actionType": "sms",
"label": "SMS Alert",
"config": {
"to": "+1234567890",
"message": "Alert: {{trigger.device_name}} entered {{trigger.geofence_name}}"
}
}
}

5. Database

Insert workflow event data into an external PostgreSQL or MySQL table.

The Database action performs parameterized inserts only — there is no raw SQL surface (no UPDATE, DELETE, or arbitrary queries). Required config: table_name. Optional config: data (an object whose keys become column names and whose values become parameterized values; defaults to the trigger payload when omitted).

{
"id": "action-1",
"type": "action",
"data": {
"actionType": "database",
"label": "Log Entry",
"config": {
"integration_id": "your-database-integration-id",
"table_name": "arrivals",
"data": {
"device_id": "{{trigger.device_id}}",
"geofence_id": "{{trigger.geofence_id}}",
"arrived_at": "{{trigger.timestamp}}"
}
}
}
}

Connection settings (host, port, database, username, password, ssl_mode) are supplied via the Database integration referenced by integration_id, not via the action config itself.

Common Workflow Patterns

Pattern 1: Multi-Channel Notification

Send alerts to multiple channels simultaneously.

{
"name": "Multi-Channel Security Alert",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["restricted_area_id"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "slack",
"label": "Slack Alert",
"config": {
"webhook_url": "https://hooks.slack.com/...",
"message": "🚨 ALERT: {{trigger.device_name}} entered restricted area"
}
}
},
{
"id": "action-2",
"type": "action",
"data": {
"actionType": "email",
"label": "Email Alert",
"config": {
"to": "security@example.com",
"subject": "Security Alert: Unauthorized Entry",
"body": "Device {{trigger.device_name}} entered restricted area at {{trigger.timestamp}}"
}
}
},
{
"id": "action-3",
"type": "action",
"data": {
"actionType": "sms",
"label": "SMS Alert",
"config": {
"to": "+1234567890",
"message": "SECURITY ALERT: {{trigger.device_name}} entered restricted area"
}
}
}
],
"edges": [
{ "id": "edge-1", "source": "trigger-1", "target": "action-1" },
{ "id": "edge-2", "source": "action-1", "target": "action-2" },
{ "id": "edge-3", "source": "action-2", "target": "action-3" }
]
}

Pattern 2: Webhook Processing

Use a webhook for custom backend logic.

{
"name": "Process Vehicle Entry",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["depot_geofence"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "webhook",
"label": "Process Checkin",
"config": {
"url": "https://your-app.com/api/vehicle-checkin",
"method": "POST",
"body": {
"vehicle_id": "{{trigger.device_id}}",
"location": {
"lat": "{{trigger.location.latitude}}",
"lng": "{{trigger.location.longitude}}"
},
"timestamp": "{{trigger.timestamp}}"
}
}
}
}
],
"edges": [{ "id": "edge-1", "source": "trigger-1", "target": "action-1" }]
}

Testing Workflows

1. Create Test Geofence

Create a small test geofence for development:

curl -X POST https://api.spatialflow.io/api/v1/geofences/ \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Geofence - Delete After Testing",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-122.4200, 37.7745],
[-122.4200, 37.7753],
[-122.4188, 37.7753],
[-122.4188, 37.7745],
[-122.4200, 37.7745]
]]
}
}'

2. Create Test Workflow

Use webhook.site for quick testing:

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Workflow",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["test_geofence_id"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "webhook",
"label": "HTTP Request",
"config": {
"url": "https://webhook.site/your-unique-id",
"method": "POST"
}
}
}
],
"edges": [
{"id": "edge-1", "source": "trigger-1", "target": "action-1"}
]
}'

3. Send Test Location

Trigger the workflow with a test location:

curl -X POST https://api.spatialflow.io/api/v1/locations \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"device_id": "test_device",
"lat": 37.7749,
"lon": -122.4194,
"ts": "2025-11-10T12:00:00Z"
}'

4. Check Webhook Deliveries

View webhook delivery history:

curl -X GET https://api.spatialflow.io/api/v1/workflows/{workflow_id}/executions \
-H "X-API-KEY: YOUR_API_KEY"

Debugging Failed Workflows

Check Workflow Execution Logs

curl -X GET https://api.spatialflow.io/api/v1/workflows/{workflow_id}/executions?status=failed \
-H "X-API-KEY: YOUR_API_KEY"

Common failure reasons:

  • Webhook endpoint returned 5xx error
  • Request timeout (> 30 seconds)
  • Invalid action configuration
  • Missing required fields

View Execution Details

curl -X GET https://api.spatialflow.io/api/v1/workflows/executions/{execution_id} \
-H "X-API-KEY: YOUR_API_KEY"

Response includes:

{
"execution_id": "exec_123",
"workflow_id": "wf_456",
"status": "failed",
"error_message": "Webhook returned 500 Internal Server Error",
"started_at": "2025-11-10T12:00:00Z",
"completed_at": "2025-11-10T12:00:05Z",
"duration_seconds": 5.0,
"current_step": 0
}

The error_message field is populated when status is failed; it contains the human-readable failure reason from the action handler.

Best Practices

1. Use Descriptive Names

Good:

  • "Notify Ops Team - Delivery Zone Entry"
  • "Log Employee Arrival - SF Office"
  • "Security Alert - Restricted Area"

Bad:

  • "Workflow 1"
  • "Test"
  • "asdf"

2. Set Workflow Active/Inactive

Disable workflows during maintenance without deleting:

# Deactivate workflow (toggle endpoint)
curl -X POST https://api.spatialflow.io/api/v1/workflows/{workflow_id}/toggle \
-H "X-API-KEY: YOUR_API_KEY"

3. Monitor Action Failures

Set up monitoring for failed actions:

# Get failed executions
curl -X GET https://api.spatialflow.io/api/v1/workflows/executions?status=failed \
-H "X-API-KEY: YOUR_API_KEY"

4. Use Idempotent Actions

Ensure actions can be safely retried:

  • Use unique IDs in database inserts
  • Check for duplicates before processing
  • Handle webhook retries gracefully

5. Keep Actions Fast

  • Webhooks should respond in < 5 seconds
  • Use async processing for slow operations
  • Return 2xx quickly to prevent retries

Performance Considerations

Workflow Execution Speed

  • Actions execute sequentially (one after another)
  • Typical execution time: 100-500ms per action
  • Webhook actions depend on your endpoint response time

Rate Limits

  • No limit on workflow count
  • No limit on executions per workflow
  • Webhook delivery rate limited to 60/min per endpoint

Scaling Tips

  • Use separate workflows for different priorities
  • Group related actions in single workflow (fewer executions)
  • Use webhooks to fan out to external services for high-volume scenarios

Real-World Examples

Example 1: Fleet Check-In System

{
"name": "Vehicle Depot Check-In",
"description": "Log vehicle arrivals and notify dispatch",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["depot_north_bay"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "slack",
"label": "Slack Notification",
"config": {
"webhook_url": "https://hooks.slack.com/...",
"message": "✅ Vehicle {{trigger.device_name}} checked in at {{trigger.geofence_name}}"
}
}
}
],
"edges": [{ "id": "edge-1", "source": "trigger-1", "target": "action-1" }]
}

Example 2: Customer Proximity Marketing

{
"name": "Store Arrival - Send Welcome Offer",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["store_42_sf"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "webhook",
"label": "HTTP Request",
"config": {
"url": "https://marketing-platform.com/api/send-offer",
"method": "POST",
"headers": {
"Authorization": "Bearer MARKETING_API_KEY"
},
"body": {
"customer_id": "{{trigger.device_id}}",
"store_id": "{{trigger.geofence.metadata.store_number}}",
"offer_type": "welcome_discount"
}
}
}
}
],
"edges": [{ "id": "edge-1", "source": "trigger-1", "target": "action-1" }]
}

Example 3: Security Perimeter Alert

{
"name": "Restricted Area - Immediate Alert",
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"data": {
"triggerType": "geofence_enter",
"label": "Geofence Entry",
"config": {
"geofence_ids": ["construction_site_restricted"]
}
}
},
{
"id": "action-1",
"type": "action",
"data": {
"actionType": "sms",
"label": "SMS Alert",
"config": {
"to": "+1234567890",
"message": "⚠️ ALERT: {{trigger.device_name}} entered restricted construction area"
}
}
},
{
"id": "action-2",
"type": "action",
"data": {
"actionType": "email",
"label": "Email Alert",
"config": {
"to": "security@example.com",
"subject": "URGENT: Restricted Area Entry",
"body": "Device: {{trigger.device_name}}\nArea: {{trigger.geofence_name}}\nTime: {{trigger.timestamp}}\nLocation: {{trigger.location.latitude}}, {{trigger.location.longitude}}"
}
}
},
{
"id": "action-3",
"type": "action",
"data": {
"actionType": "webhook",
"label": "HTTP Request",
"config": {
"url": "https://security-system.com/api/alerts",
"method": "POST"
}
}
}
],
"edges": [
{ "id": "edge-1", "source": "trigger-1", "target": "action-1" },
{ "id": "edge-2", "source": "action-1", "target": "action-2" },
{ "id": "edge-3", "source": "action-2", "target": "action-3" }
]
}

Next Steps