Skip to main content

Vehicle Tracking Guide

Build an end-to-end vehicle tracking system with SpatialFlow. This guide walks through creating depot geofences, setting up entry/exit workflows, sending location updates, and receiving real-time notifications.

Use Case

Track delivery vehicles entering and exiting depot locations:

  • Automatically log vehicle check-ins
  • Notify dispatch team of arrivals/departures
  • Calculate dwell time at depots
  • Monitor fleet movements in real-time

Architecture Overview

Mobile App → Location Updates → SpatialFlow API

Geofence Engine

Workflow Triggers

Webhook → Your Backend

Dispatch Dashboard

Step 1: Create Depot Geofences

Create geofences for each depot location.

Via API

curl -X POST https://api.spatialflow.io/api/v1/geofences \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Depot - North Bay",
"description": "Primary vehicle depot for Bay Area operations",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-122.4200, 37.7745],
[-122.4200, 37.7753],
[-122.4188, 37.7753],
[-122.4188, 37.7745],
[-122.4200, 37.7745]
]]
},
"metadata": {
"depot_code": "NB001",
"region": "bay_area",
"capacity": 50,
"manager": "operations@example.com"
}
}'

Response:

{
"id": "gf_depot_north_bay",
"name": "Depot - North Bay",
"geometry": { ... },
"metadata": {
"depot_code": "NB001",
"region": "bay_area"
},
"is_active": true,
"created_at": "2025-11-10T10:00:00Z"
}

Via Dashboard

  1. Go to GeofencesCreate New
  2. Use the polygon tool to draw around the depot
  3. Fill in name and metadata
  4. Click Save

Step 2: Set Up Entry/Exit Workflows

Create workflows to handle vehicle arrivals and departures.

Simplified Payload

The workflow examples in this guide use a simplified trigger/actions format for readability. The actual API uses a nodes and edges format. See Create Your First Workflow for the exact API schema.

Vehicle Check-In Workflow (Entry)

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Vehicle Check-In - North Bay Depot",
"description": "Log vehicle arrivals and notify dispatch",
"trigger": {
"type": "geofence_entry",
"geofence_id": "gf_depot_north_bay"
},
"actions": [
{
"type": "webhook",
"config": {
"url": "https://your-backend.com/api/vehicle-checkin",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_BACKEND_API_KEY"
},
"body": {
"vehicle_id": "{{device.id}}",
"vehicle_name": "{{device.name}}",
"depot_id": "{{geofence.id}}",
"depot_name": "{{geofence.name}}",
"depot_code": "{{geofence.metadata.depot_code}}",
"checkin_time": "{{event.timestamp}}",
"location": {
"lat": "{{location.latitude}}",
"lng": "{{location.longitude}}"
}
}
}
},
{
"type": "slack",
"config": {
"webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"message": "✅ Vehicle {{device.name}} checked in at {{geofence.name}}",
"channel": "#dispatch"
}
}
]
}'

Vehicle Check-Out Workflow (Exit)

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Vehicle Check-Out - North Bay Depot",
"description": "Log vehicle departures",
"trigger": {
"type": "geofence_exit",
"geofence_id": "gf_depot_north_bay"
},
"actions": [
{
"type": "webhook",
"config": {
"url": "https://your-backend.com/api/vehicle-checkout",
"method": "POST",
"body": {
"vehicle_id": "{{device.id}}",
"depot_id": "{{geofence.id}}",
"checkout_time": "{{event.timestamp}}"
}
}
},
{
"type": "slack",
"config": {
"webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"message": "🚪 Vehicle {{device.name}} departed from {{geofence.name}}",
"channel": "#dispatch"
}
}
]
}'

Step 3: Register Vehicles as Devices

Register each vehicle in your fleet.

curl -X POST https://api.spatialflow.io/api/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"device_id": "vehicle_101",
"name": "Delivery Van 101",
"metadata": {
"license_plate": "ABC123",
"driver": "John Doe",
"vehicle_type": "van",
"capacity_kg": 1000
}
}'

Bulk Registration

Register multiple vehicles via CSV upload in the Dashboard. For API usage, create devices individually with POST /devices (one per vehicle).

Step 4: Send Location Updates

Send vehicle location updates from your mobile app or GPS tracker.

Single Location Update

Use the device UUID (id) returned from the registration response.

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/location \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"latitude": 37.7749,
"longitude": -122.4194,
"accuracy": 10,
"timestamp": "2025-11-10T12:00:00Z",
"metadata": {
"speed_kmh": 45,
"heading": 180
}
}'

Batch Location Updates

Send multiple updates efficiently:

curl -X POST https://api.spatialflow.io/api/v1/devices/batch-update \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{
"device_id": "vehicle_101",
"locations": [
{
"latitude": 37.7749,
"longitude": -122.4194,
"timestamp": "2025-11-10T12:00:00Z"
}
]
},
{
"device_id": "vehicle_102",
"locations": [
{
"latitude": 37.7850,
"longitude": -122.4100,
"timestamp": "2025-11-10T12:00:05Z"
}
]
}
]'

Mobile App Integration (React Native Example)

import * as Location from 'expo-location';

// Request location permissions
const { status } = await Location.requestForegroundPermissionsAsync();

// Start tracking
Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
timeInterval: 30000, // Every 30 seconds
distanceInterval: 100, // Every 100 meters
},
async (location) => {
// Send to SpatialFlow
await fetch('https://api.spatialflow.io/api/v1/devices/{device_uuid}/location', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
accuracy: location.coords.accuracy,
timestamp: new Date(location.timestamp).toISOString(),
metadata: {
speed_kmh: location.coords.speed * 3.6, // Convert m/s to km/h
heading: location.coords.heading,
},
}),
});
}
);

Step 5: Handle Webhook Events

Build a backend endpoint to receive check-in/check-out events.

Express (Node.js) Backend

const express = require('express');
const app = express();

app.use(express.json());

// Vehicle check-in endpoint
app.post('/api/vehicle-checkin', async (req, res) => {
const {
vehicle_id,
vehicle_name,
depot_id,
depot_name,
depot_code,
checkin_time,
location
} = req.body;

// Log to database
await db.vehicleCheckins.insert({
vehicle_id,
depot_id,
depot_code,
checkin_time: new Date(checkin_time),
latitude: location.lat,
longitude: location.lng,
});

// Update vehicle status
await db.vehicles.update(vehicle_id, {
status: 'at_depot',
current_depot: depot_id,
last_checkin: new Date(checkin_time),
});

// Calculate any outstanding deliveries
const pendingDeliveries = await db.deliveries.find({
vehicle_id,
status: 'in_transit',
});

console.log(`${vehicle_name} checked in at ${depot_name}`);
console.log(` Pending deliveries: ${pendingDeliveries.length}`);

res.json({ success: true, vehicle_id });
});

// Vehicle check-out endpoint
app.post('/api/vehicle-checkout', async (req, res) => {
const { vehicle_id, depot_id, checkout_time } = req.body;

// Calculate dwell time
const checkin = await db.vehicleCheckins.findLast({ vehicle_id, depot_id });
const dwellMinutes = (new Date(checkout_time) - new Date(checkin.checkin_time)) / 60000;

// Log checkout
await db.vehicleCheckouts.insert({
vehicle_id,
depot_id,
checkout_time: new Date(checkout_time),
dwell_minutes: dwellMinutes,
});

// Update vehicle status
await db.vehicles.update(vehicle_id, {
status: 'on_route',
current_depot: null,
last_checkout: new Date(checkout_time),
});

console.log(`🚪 Vehicle ${vehicle_id} departed after ${dwellMinutes} minutes`);

res.json({ success: true, dwell_minutes: dwellMinutes });
});

app.listen(3000, () => console.log('Backend listening on port 3000'));

Best Practices

1. Update Frequency

Balance accuracy with battery life and data usage:

Recommended intervals:

  • High accuracy mode: Every 10-30 seconds or 50-100 meters
  • Normal mode: Every 30-60 seconds or 100-200 meters
  • Power saver mode: Every 2-5 minutes or 500+ meters

2. Accuracy Filtering

Filter out low-accuracy readings:

// Only send if accuracy is acceptable
if (location.coords.accuracy < 50) { // Within 50 meters
await sendLocationUpdate(location);
}

3. Offline Handling

Queue updates when offline:

const queue = [];

async function sendLocationUpdate(location) {
try {
await fetch(API_URL, { ... });
} catch (error) {
// Store for later if offline
queue.push(location);
}
}

// Retry queued updates when back online
window.addEventListener('online', async () => {
while (queue.length > 0) {
const location = queue.shift();
await sendLocationUpdate(location);
}
});

4. Geofence Sizing

Size depot geofences appropriately:

  • Too small: Vehicles might not trigger if GPS drifts
  • Too large: Early/late triggers
  • Recommended: 50-100 meter radius around depot boundary

5. Dwell Time Calculation

Calculate time spent at depot:

const checkinTime = new Date(checkin.timestamp);
const checkoutTime = new Date(checkout.timestamp);
const dwellMinutes = (checkoutTime - checkinTime) / 60000;

console.log(`Vehicle spent ${dwellMinutes} minutes at depot`);

Monitoring and Reporting

View Active Vehicles

curl -X GET https://api.spatialflow.io/api/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY"

Check Geofence Events

curl -X GET https://api.spatialflow.io/api/v1/workflows/{workflow_id}/executions?limit=50 \
-H "Authorization: Bearer YOUR_API_KEY"

Dashboard Metrics

Track these KPIs:

  • Average dwell time per depot
  • Vehicle utilization (time on route vs. at depot)
  • Check-in/check-out frequency
  • Geofence entry/exit counts

Troubleshooting

Vehicle Not Triggering Geofence

Check:

  1. GPS accuracy is < 50 meters
  2. Vehicle actually crosses geofence boundary
  3. Geofence is active (is_active: true)
  4. Workflow is active and configured correctly

Test with point-in-polygon:

curl -X POST https://api.spatialflow.io/api/v1/geofences/test-point \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"geometry": {
"type": "Point",
"coordinates": [-122.4194, 37.7749]
}
}'

Duplicate Notifications

Cause: Vehicle GPS bounces at geofence boundary

Solution: Add dwell time filter or use distance hysteresis:

  • Only trigger if vehicle stays inside for > 30 seconds
  • Require minimum distance from boundary before exit triggers

Delayed Notifications

Check:

  • Webhook endpoint response time (should be < 5 seconds)
  • Network connectivity
  • SpatialFlow workflow execution logs

Next Steps