Skip to main content

Asset Monitoring Guide

Build an end-to-end asset monitoring system with SpatialFlow. This guide walks through tracking high-value equipment and assets, setting up boundary geofences for authorized storage areas, detecting unauthorized movement with exit alerts, and automating theft prevention workflows with instant notifications.

Use Case

Track and protect high-value equipment across facilities and job sites:

  • Track high-value equipment across job sites and warehouses
  • Create boundary geofences for authorized storage areas
  • Detect unauthorized asset movement with exit alerts
  • Automate theft prevention workflows with instant notifications
  • Monitor asset dwell time and utilization across locations
  • Integrate with security systems via webhooks

Architecture Overview

Asset Tags/GPS --> Location Updates --> SpatialFlow API
|
Boundary Geofences
|
Geofence Engine
|
Workflow Triggers
/ \
Boundary Alert Normal Movement
|
Webhook --> Security Backend
/ \
SMS/Email Alert Lock Asset

Step 1: Create Boundary Geofences

Define boundaries around authorized asset storage locations. Each boundary type serves a different security purpose.

Warehouse/Yard Boundary

Create a perimeter around a storage facility to detect when assets leave the premises.

curl -X POST https://api.spatialflow.io/api/v1/geofences \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Houston Warehouse - Main Yard",
"description": "Primary equipment storage yard perimeter",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-95.3710, 29.7604],
[-95.3710, 29.7620],
[-95.3685, 29.7620],
[-95.3685, 29.7604],
[-95.3710, 29.7604]
]]
},
"metadata": {
"site_id": "WH-HOU-001",
"site_name": "Houston Main Warehouse",
"security_level": "high",
"operating_hours": "06:00-22:00",
"boundary_type": "warehouse"
}
}'

Response:

{
"id": "gf_wh_houston_main",
"name": "Houston Warehouse - Main Yard",
"geometry": { "..." },
"metadata": {
"site_id": "WH-HOU-001",
"site_name": "Houston Main Warehouse",
"security_level": "high",
"operating_hours": "06:00-22:00",
"boundary_type": "warehouse"
},
"is_active": true,
"created_at": "2025-11-10T10:00:00Z"
}

Job Site Boundary

Create a temporary perimeter around an active job site where assets are deployed.

curl -X POST https://api.spatialflow.io/api/v1/geofences \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Katy Freeway Extension - Site B",
"description": "Active construction site for highway expansion project",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-95.4900, 29.7780],
[-95.4900, 29.7810],
[-95.4850, 29.7810],
[-95.4850, 29.7780],
[-95.4900, 29.7780]
]]
},
"metadata": {
"project_id": "PROJ-2025-042",
"project_name": "Katy Freeway Extension",
"start_date": "2025-03-01",
"end_date": "2025-09-30",
"boundary_type": "job_site"
}
}'

Restricted Zone

Define a high-security area within a larger facility for sensitive or high-value equipment.

curl -X POST https://api.spatialflow.io/api/v1/geofences \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Houston Warehouse - Secure Storage",
"description": "Restricted zone for high-value generators and heavy equipment",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-95.3705, 29.7608],
[-95.3705, 29.7614],
[-95.3695, 29.7614],
[-95.3695, 29.7608],
[-95.3705, 29.7608]
]]
},
"metadata": {
"zone_type": "restricted",
"clearance_required": "level_3",
"alarm_enabled": true,
"parent_site_id": "WH-HOU-001",
"boundary_type": "restricted_zone"
}
}'

Step 2: Set Up Alert Workflows

Configure workflows that trigger when assets cross boundary geofences.

Unauthorized Exit Alert (HIGH PRIORITY)

Triggers when any asset leaves the warehouse boundary. This is the primary theft prevention workflow.

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Unauthorized Exit - Houston Warehouse",
"description": "HIGH PRIORITY: Alert when asset leaves warehouse boundary",
"trigger": {
"type": "geofence_exit",
"geofence_id": "gf_wh_houston_main"
},
"actions": [
{
"type": "webhook",
"config": {
"url": "https://your-backend.com/api/boundary-alert",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_BACKEND_API_KEY"
},
"body": {
"alert_type": "unauthorized_exit",
"priority": "HIGH",
"asset_id": "{{device.id}}",
"asset_name": "{{device.name}}",
"boundary_id": "{{geofence.id}}",
"boundary_name": "{{geofence.name}}",
"exit_time": "{{event.timestamp}}",
"last_known_location": {
"lat": "{{location.latitude}}",
"lng": "{{location.longitude}}"
},
"site_id": "{{geofence.metadata.site_id}}",
"operating_hours": "{{geofence.metadata.operating_hours}}"
}
}
},
{
"type": "sms",
"config": {
"to": ["+15551234567", "+15559876543"],
"message": "ALERT: Asset {{device.name}} has left {{geofence.name}} at {{event.timestamp}}. Verify authorization immediately."
}
},
{
"type": "slack",
"config": {
"webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"message": "BOUNDARY ALERT: Asset {{device.name}} exited {{geofence.name}}",
"channel": "#security-alerts"
}
}
]
}'

Asset Arrival Workflow

Triggers when an asset arrives at a job site. Logs the delivery and updates inventory.

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Asset Arrival - Katy Freeway Site",
"description": "Log asset delivery to job site",
"trigger": {
"type": "geofence_entry",
"geofence_id": "gf_katy_site_b"
},
"actions": [
{
"type": "webhook",
"config": {
"url": "https://your-backend.com/api/asset-arrival",
"method": "POST",
"body": {
"asset_id": "{{device.id}}",
"asset_name": "{{device.name}}",
"site_id": "{{geofence.metadata.project_id}}",
"site_name": "{{geofence.metadata.project_name}}",
"arrival_time": "{{event.timestamp}}",
"location": {
"lat": "{{location.latitude}}",
"lng": "{{location.longitude}}"
}
}
}
}
]
}'

After-Hours Movement Alert

Triggers when any asset leaves a boundary outside operating hours. Compare the event timestamp against the operating_hours metadata on the geofence to determine if the movement is authorized.

curl -X POST https://api.spatialflow.io/api/v1/workflows \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "After-Hours Movement Detection",
"description": "Detect asset movement outside operating hours",
"trigger": {
"type": "geofence_exit",
"geofence_id": "gf_wh_houston_main"
},
"actions": [
{
"type": "webhook",
"config": {
"url": "https://your-backend.com/api/after-hours-check",
"method": "POST",
"body": {
"asset_id": "{{device.id}}",
"asset_name": "{{device.name}}",
"boundary_id": "{{geofence.id}}",
"event_time": "{{event.timestamp}}",
"operating_hours": "{{geofence.metadata.operating_hours}}",
"security_level": "{{geofence.metadata.security_level}}"
}
}
}
]
}'

Tip: For automated after-hours anomaly detection based on historical movement patterns, see Signals. Signals can learn normal asset movement windows and flag deviations without manual operating-hours configuration.

Step 3: Register Assets as Devices

Register each tracked asset with descriptive metadata for identification and classification.

Heavy Equipment

curl -X POST https://api.spatialflow.io/api/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"device_id": "asset_gen_500",
"name": "Generator 500kW - Unit A",
"metadata": {
"asset_type": "generator",
"serial_number": "GEN-2024-50021",
"value_usd": 85000,
"assigned_site": "WH-HOU-001",
"last_maintenance": "2025-08-15",
"weight_kg": 4500,
"fuel_type": "diesel"
}
}'

Construction Equipment

curl -X POST https://api.spatialflow.io/api/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"device_id": "asset_exc_102",
"name": "Excavator CAT 320 - Unit B",
"metadata": {
"asset_type": "excavator",
"serial_number": "EXC-2023-10245",
"value_usd": 250000,
"assigned_site": "PROJ-2025-042",
"last_maintenance": "2025-09-01",
"operating_weight_kg": 22000,
"manufacturer": "Caterpillar"
}
}'

Step 4: Send Location Updates

Integrate GPS trackers or asset tags to send location updates to SpatialFlow.

Single Location Update

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": 29.7612,
"longitude": -95.3698,
"accuracy": 5,
"timestamp": "2025-11-10T14:30:00Z",
"metadata": {
"battery_level": 87,
"signal_strength": -72,
"tracker_type": "cellular"
}
}'

Update Frequency Strategy

Asset trackers should adjust their reporting frequency based on context:

Stationary assets (equipment stored in yard):

  • Report every 5-15 minutes
  • Conserves battery while maintaining boundary awareness
  • Increase frequency if motion is detected

Assets in transit (being transported between sites):

  • Report every 30 seconds
  • Provides continuous tracking during movement
  • Enables route monitoring and theft detection
// Tracker firmware logic (pseudocode)
if (motionDetected()) {
// Switch to high-frequency mode
setReportingInterval(30); // seconds
sendUpdate({ latitude, longitude, metadata: { motion: true } });
} else {
// Stationary mode - conserve battery
setReportingInterval(600); // 10 minutes
sendUpdate({ latitude, longitude, metadata: { motion: false } });
}

Hardware Tracker Integration

SpatialFlow accepts location updates from any GPS-capable device via the REST API. Common tracker types:

Tracker TypeBest ForBattery LifeAccuracy
Cellular GPSHigh-value mobile assets1-3 years3-10m
LoRaWANLarge-area fixed sites3-5 years10-50m
BLE BeaconsIndoor/warehouse tracking1-2 years1-5m
SatelliteRemote/off-grid locations1-2 years5-15m

Most trackers can be configured to POST location data to your middleware, which then forwards updates to the SpatialFlow API in the expected format.

Step 5: Handle Webhook Events

Build backend endpoints to process boundary alerts and asset movement events.

Express.js Security Backend

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

app.use(express.json());

// CRITICAL: Boundary alert handler
app.post('/api/boundary-alert', async (req, res) => {
const {
alert_type,
priority,
asset_id,
asset_name,
boundary_id,
boundary_name,
exit_time,
last_known_location,
site_id,
operating_hours,
} = req.body;

// Check if movement is during operating hours
const isAuthorized = checkOperatingHours(exit_time, operating_hours);

if (!isAuthorized) {
console.log(`UNAUTHORIZED EXIT: ${asset_name} left ${boundary_name} outside operating hours`);

// Cross-reference with dispatch system
const hasApprovedTransfer = await db.transfers.findOne({
asset_id,
status: 'approved',
scheduled_date: new Date(exit_time).toISOString().split('T')[0],
});

if (!hasApprovedTransfer) {
// UNAUTHORIZED MOVEMENT - trigger lockdown
await triggerLockdown(asset_id, {
reason: 'unauthorized_exit',
boundary_id,
exit_time,
location: last_known_location,
});

// Alert security team
await notifySecurityTeam({
type: 'theft_alert',
asset_id,
asset_name,
last_known_location,
exit_time,
site_id,
});

// Log incident
await db.incidents.insert({
type: 'unauthorized_movement',
asset_id,
boundary_id,
exit_time: new Date(exit_time),
location: last_known_location,
status: 'open',
priority: 'critical',
});
}
}

res.json({ received: true, authorized: isAuthorized });
});

// Asset arrival handler
app.post('/api/asset-arrival', async (req, res) => {
const { asset_id, asset_name, site_id, site_name, arrival_time, location } = req.body;

// Update inventory
await db.inventory.upsert({
asset_id,
current_site: site_id,
arrived_at: new Date(arrival_time),
status: 'on_site',
});

// Confirm delivery against manifest
const manifest = await db.transferManifests.findOne({
asset_id,
destination_site: site_id,
status: 'in_transit',
});

if (manifest) {
await db.transferManifests.update(manifest.id, {
status: 'delivered',
actual_arrival: new Date(arrival_time),
});
console.log(`Delivery confirmed: ${asset_name} arrived at ${site_name}`);
}

res.json({ received: true, asset_id });
});

// Asset departure handler
app.post('/api/asset-departure', async (req, res) => {
const { asset_id, asset_name, site_id, departure_time } = req.body;

// Check against approved transfers
const approvedTransfer = await db.transfers.findOne({
asset_id,
origin_site: site_id,
status: 'approved',
});

if (!approvedTransfer) {
console.warn(`UNAPPROVED DEPARTURE: ${asset_name} leaving ${site_id}`);
await db.incidents.insert({
type: 'unapproved_departure',
asset_id,
site_id,
departure_time: new Date(departure_time),
status: 'open',
});
}

// Log departure
await db.inventory.update({
asset_id,
current_site: site_id,
}, {
status: 'departed',
departed_at: new Date(departure_time),
});

res.json({ received: true, approved: !!approvedTransfer });
});

// Helper: Check if time falls within operating hours
function checkOperatingHours(eventTime, operatingHours) {
if (!operatingHours) return true; // No restriction

const [start, end] = operatingHours.split('-');
const eventDate = new Date(eventTime);
const eventHour = eventDate.getUTCHours();
const eventMinute = eventDate.getUTCMinutes();
const eventTimeMinutes = eventHour * 60 + eventMinute;

const [startH, startM] = start.split(':').map(Number);
const [endH, endM] = end.split(':').map(Number);
const startMinutes = startH * 60 + startM;
const endMinutes = endH * 60 + endM;

return eventTimeMinutes >= startMinutes && eventTimeMinutes <= endMinutes;
}

// Helper: Trigger asset lockdown
async function triggerLockdown(assetId, details) {
// Send lock command to asset tracker (if supported)
await fetch(`https://tracker-api.example.com/assets/${assetId}/lock`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(details),
});
}

// Helper: Notify security team
async function notifySecurityTeam(alert) {
await fetch('https://your-sms-service.com/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: ['+15551234567', '+15559876543'],
message: `THEFT ALERT: ${alert.asset_name} unauthorized movement detected at ${alert.exit_time}. Last location: ${alert.last_known_location.lat}, ${alert.last_known_location.lng}`,
}),
});
}

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

Theft Prevention Patterns

Advanced strategies for protecting high-value assets using SpatialFlow's geofencing capabilities.

Geofence Layering

Create multiple concentric boundaries around high-value assets. The outer boundary triggers a warning; the inner boundary triggers a lockdown.

# Outer warning zone (larger perimeter)
curl -X POST https://api.spatialflow.io/api/v1/geofences \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Houston Warehouse - Warning Zone",
"description": "Outer perimeter warning zone",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-95.3720, 29.7598],
[-95.3720, 29.7626],
[-95.3675, 29.7626],
[-95.3675, 29.7598],
[-95.3720, 29.7598]
]]
},
"metadata": {
"zone_type": "warning",
"parent_site_id": "WH-HOU-001",
"alert_level": "medium"
}
}'

# Inner secure zone (tight perimeter) — reuses the warehouse boundary
# When asset exits inner zone: immediate HIGH alert
# When asset exits outer zone: CRITICAL escalation

Create separate workflows for each layer:

  • Inner zone exit triggers a HIGH priority investigation alert
  • Outer zone exit triggers a CRITICAL lockdown with security dispatch

Time-Based Rules

Compare exit events against operating hours stored in geofence metadata.

function evaluateExitEvent(event, geofenceMetadata) {
const exitTime = new Date(event.timestamp);
const { operating_hours, security_level } = geofenceMetadata;

const isWithinHours = checkOperatingHours(event.timestamp, operating_hours);
const isWeekend = [0, 6].includes(exitTime.getUTCDay());

if (!isWithinHours || isWeekend) {
return {
authorized: false,
reason: isWeekend ? 'weekend_movement' : 'after_hours_movement',
severity: security_level === 'high' ? 'critical' : 'high',
};
}

return { authorized: true };
}

Velocity Detection

If a stationary asset suddenly moves at vehicle speed (e.g., a generator on a truck), flag it as potential theft. Include speed data in location update metadata.

function checkVelocityAnomaly(asset, locationUpdate) {
const { speed_kmh } = locationUpdate.metadata || {};
const { asset_type } = asset.metadata || {};

// Expected max speeds by asset type
const maxExpectedSpeeds = {
generator: 5, // Should not move on its own
excavator: 15, // Slow-moving equipment
trailer: 10, // Parked trailers
tool_chest: 3, // Essentially stationary
};

const maxSpeed = maxExpectedSpeeds[asset_type] || 10;

if (speed_kmh && speed_kmh > maxSpeed) {
return {
anomaly: true,
reason: `${asset_type} moving at ${speed_kmh} km/h (max expected: ${maxSpeed} km/h)`,
severity: speed_kmh > maxSpeed * 3 ? 'critical' : 'high',
};
}

return { anomaly: false };
}

Multi-Asset Correlation

If multiple assets leave the same boundary within a short time window, escalate the alert priority. This pattern detects organized theft attempts.

async function checkMultiAssetExit(boundaryId, exitTime, windowMinutes = 15) {
const windowStart = new Date(new Date(exitTime) - windowMinutes * 60 * 1000);

const recentExits = await db.boundaryEvents.find({
boundary_id: boundaryId,
event_type: 'exit',
timestamp: { $gte: windowStart, $lte: new Date(exitTime) },
});

if (recentExits.length >= 3) {
return {
escalate: true,
reason: `${recentExits.length} assets exited ${boundaryId} within ${windowMinutes} minutes`,
severity: 'critical',
affected_assets: recentExits.map(e => e.asset_id),
};
}

return { escalate: false, recent_count: recentExits.length };
}

Best Practices

1. Boundary Sizing

Size geofences based on asset value and mobility:

  • High-value stationary assets (generators, heavy equipment): Tight boundaries, 10-20 meter buffer
  • Mobile equipment (vehicles, trailers): Wider boundaries, 50-100 meter buffer
  • Indoor assets (tool chests, electronics): Use BLE beacon zones instead of GPS boundaries

2. Update Frequency by Asset Type

Balance tracking accuracy with battery life:

Asset TypeStationary IntervalIn-Transit IntervalNotes
Heavy equipment10-15 min30 secHigh-value, long battery
Vehicles/trailers5 min15-30 secFrequent movement
Portable tools30 min1-2 minBattery conservation priority
BLE-tagged itemsContinuous (passive)N/ABeacon-based, no battery drain

3. Battery Management

GPS trackers have limited battery life. Optimize reporting to maximize uptime:

  • Use motion-activated reporting (sleep when stationary, wake on movement)
  • Monitor battery level in location metadata and alert at 20% threshold
  • Schedule periodic "heartbeat" pings even when stationary (every 30-60 minutes)
  • Set up a workflow to alert operations when battery_level drops below threshold

4. Geofence Metadata for Authorization

Store authorization rules directly in geofence metadata for real-time evaluation:

  • operating_hours: Time window for authorized movement
  • clearance_required: Minimum clearance level to move assets
  • alarm_enabled: Whether boundary exit triggers physical alarms
  • authorized_carriers: List of approved transport companies

5. Alert Fatigue Prevention

Prevent security teams from ignoring alerts due to volume:

  • Severity levels: Only page on-call for CRITICAL alerts; batch LOW/MEDIUM for daily review
  • Grouping: Combine related alerts (e.g., asset exits inner then outer zone = one incident)
  • Quiet hours for known movements: Suppress alerts for pre-approved transfers
  • Cooldown periods: After an alert is acknowledged, suppress duplicates for 15 minutes

Monitoring and Reporting

Asset Inventory

List all tracked assets with metadata filters:

curl -X GET "https://api.spatialflow.io/api/v1/devices?metadata.asset_type=generator" \
-H "Authorization: Bearer YOUR_API_KEY"

Boundary Event History

Review recent workflow executions for a boundary alert workflow:

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

Dashboard KPIs

Track these metrics for asset monitoring effectiveness:

KPIDescriptionTarget
Assets in boundaryPercentage of assets currently within their assigned boundary> 98%
Unauthorized movement countNumber of boundary exits without approved transfer0 per week
Avg time-on-siteAverage duration assets spend at each locationVaries by project
Asset utilization ratePercentage of time assets are actively in use vs. idle> 70%
Alert response timeTime from boundary alert to security acknowledgment< 5 minutes

Troubleshooting

False Boundary Alerts

Cause: GPS drift causes stationary assets to appear outside their boundary, especially near walls or under metal structures.

Solutions:

  • Add a 10-20 meter buffer to boundary geofences
  • Require two consecutive outside-boundary readings before triggering an alert
  • Use accuracy filtering: ignore updates with accuracy > 30 meters
  • For warehouse interiors, supplement GPS with BLE beacons

Tracker Battery Dies

Cause: GPS reporting frequency drains battery faster than expected.

Solutions:

  • Monitor battery_level in location metadata
  • Set up a workflow alert when battery drops below 20%
  • Reduce reporting frequency for low-priority assets
  • Schedule battery replacement based on historical drain rates

Indoor Tracking Limitations

Cause: GPS signals are weak or unavailable inside warehouses and covered facilities.

Solutions:

  • Deploy BLE beacons at warehouse entry/exit points for zone detection
  • Use Wi-Fi positioning as a fallback for indoor areas
  • Place GPS boundary geofences at building entrances rather than around equipment bays
  • Combine GPS (outdoor) with BLE (indoor) for full coverage

Next Steps

  • Geofences — Learn about geofence types, properties, and spatial queries
  • Workflows — Build multi-step automations with conditional logic
  • Devices — Advanced device management and metadata
  • Webhook Integration — Build robust webhook receivers with retries
  • Error Handling — Handle API errors and edge cases gracefully
  • Signals — Configure anomaly detection for asset movement patterns
  • Best Practices — Production security patterns and performance tuning