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
- Go to Geofences → Create New
- Use the polygon tool to draw around the depot
- Fill in name and metadata
- Click Save
Step 2: Set Up Entry/Exit Workflows
Create workflows to handle vehicle arrivals and departures.
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:
- GPS accuracy is < 50 meters
- Vehicle actually crosses geofence boundary
- Geofence is active (
is_active: true) - 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
- Geofences - Learn more about geofence types and properties
- Workflows - Build complex multi-step automations
- Devices - Advanced device management
- Webhook Integration - Build robust webhook receivers
- Error Handling - Handle failures gracefully