Webhook documentation
BetaEverything your endpoint needs to receive, verify and process FleetBold events.
Overview
A webhook is an HTTPS endpoint you control. Once a webhook exists and vehicles are assigned to it, FleetBold pushes a signed JSON request with each vehicle's location every time it processes one, or at most once per vehicle per interval if you set one. You never poll: the data comes to you.
- Event type in this version:
vehicle.location(GPS point, heading, speed, ignition, odometer and battery when the device reports them). - One event per request by default; every request carries a timestamp and an HMAC signature.
- Webhooks belong to an organization. During the beta each organization can have one webhook.
Quick start
- Create the webhook from Webhooks & API → New webhook: HTTPS URL, authentication (an HMAC signing secret is recommended: press Generate and store it), frequency (every event by default) and delivery mode.
- Send a test event from the webhook card menu. Your endpoint must answer 2xx.
- Assign vehicles (Vehicles on the card): only vehicles with a connected tracker or built-in tracking can report.
- Verify every request with the signature (see below) and deduplicate by event id.
The request
FleetBold sends a POST (or PUT, your choice) with a compact JSON body, no whitespace. Redirects are never followed; the request times out after your configured timeout (1 to 30 s, default 10).
POST /fleetbold HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: FleetBold-DataForwarding/1
X-FleetBold-Timestamp: 1790161121
X-FleetBold-Signature: v1=3f9a0c...e2b1
X-FleetBold-Event-Id: evt_5d5c0de77a6b4f738ae7a518f71fe921
X-FleetBold-Stream-Id: 12
Authorization: Bearer <your token> (only with "Bearer token" auth)
{"type":"vehicle.location","occurred_at":"2026-09-22T22:58:40Z","vehicle":{...},"data":{...},"source":{...},"organization_id":42,"id":"evt_...","version":"fleetbold.v1","sent_at":"2026-09-22T22:58:41Z"}| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | FleetBold-DataForwarding/1 |
X-FleetBold-Timestamp | Unix time in seconds when the request was signed. |
X-FleetBold-Signature | v1= followed by hex HMAC-SHA256 of timestamp + "." + body. Always present. |
X-FleetBold-Event-Id | The event id (same as id in the body). Use it to deduplicate. |
X-FleetBold-Stream-Id | Numeric id of your webhook. |
Authorization | Bearer <token> with Bearer token auth, Basic <base64> with Basic auth. |
X-API-Key (configurable) | Your token, with API key header auth. The header name is yours to choose. |
Batching is off in the beta (one event per request). When batching is enabled, several events travel as {"events": [ ... ]}, or under your mapping's root key.
Event payload
The default format is the FleetBold envelope, fleetbold.v1. Field mapping (below) can reshape it to whatever your endpoint expects.
{
"id": "evt_5d5c0de77a6b4f738ae7a518f71fe921",
"type": "vehicle.location",
"version": "fleetbold.v1",
"occurred_at": "2026-09-22T22:58:40Z",
"sent_at": "2026-09-22T22:58:41Z",
"organization_id": 42,
"vehicle": {
"id": 123,
"vin": "1FT7W2BT8HEB21938",
"imei": "860000000000001",
"nickname": "Van 3",
"plate": "ABC123",
"make": "Ford",
"model": "F-150",
"year": 2019
},
"data": {
"lat": 25.667,
"lon": -80.398,
"heading": 270,
"speed_mph": 26.1,
"speed_kmh": 42.0,
"ignition": true,
"odometer_mi": 52331.4,
"battery_level": 78
},
"source": { "family": "fleetbold", "device_id": 9001 }
}| Field | Meaning |
|---|---|
id | Unique event id (evt_…). Stable across retries of the same event. |
type / version | vehicle.location / fleetbold.v1. |
occurred_at | When the device recorded the point (ISO-8601, UTC, Z). |
sent_at | When FleetBold sent this request. |
organization_id | The organization the webhook belongs to. |
vehicle | id, vin, imei (tracker, when any), nickname, plate, make, model, year. Only what is known is present. |
data | lat, lon always; heading (0–359), speed_mph, speed_kmh, ignition, odometer_mi, battery_level, fuel_level, outside_temp_c, signal_strength, gps_fix, locked when the device reported them. |
source | Which device produced the point: family (fleetbold, bouncie, tesla, …) and its identifier when available. |
Treat unknown fields as additive: new keys may appear inside data and vehiclewithout a version change. Existing keys never change meaning within fleetbold.v1.
Verifying the signature
Compute HMAC-SHA256(secret, timestamp + "." + raw body), hex-encode it, prefixv1= and compare with X-FleetBold-Signature using a constant-time comparison. Sign the raw request bytes, never a re-serialized JSON. Reject timestamps older than a few minutes to stop replays.
| Authentication | Signing secret |
|---|---|
| HMAC signing secret | The signing secret (recommended: Generate creates a whsec_… value shown once). |
| Bearer token / API key header | The token. |
| Basic auth | The password. |
| None | A key managed by FleetBold that you do not hold: the header is present but you cannot verify it. Choose HMAC to verify. |
import express from 'express';
import crypto from 'node:crypto';
const app = express();
const SECRET = process.env.FLEETBOLD_WEBHOOK_SECRET; // the signing secret you configured
// Keep the RAW body: the signature covers the exact bytes FleetBold sent.
app.post('/fleetbold', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.header('X-FleetBold-Timestamp') || '';
const received = req.header('X-FleetBold-Signature') || '';
const expected = 'v1=' + crypto
.createHmac('sha256', SECRET)
.update(ts + '.')
.update(req.body) // Buffer
.digest('hex');
const sameLength = received.length === expected.length;
const valid = sameLength && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300; // 5 minutes
if (!valid || !fresh) return res.status(401).end();
const event = JSON.parse(req.body.toString('utf8'));
// Deduplicate by event.id (at-least-once delivery can repeat a batch).
// ... your logic ...
res.status(200).end();
});
app.listen(3000);Secrets are encrypted at rest and shown only once, when you create or change them. To rotate, edit the webhook and set a new one; requests are signed with the new secret from then on.
Responding & retries
Answer quickly with any 2xx once you have stored the event; do your processing afterwards. FleetBold reads at most the first 4 KB of your response, only for the error log.
| Your response | FleetBold treats it as | What happens |
|---|---|---|
| 2xx | accepted | Done. Nothing is stored on our side. |
| 408, 425, 429, 500, 502, 503, 504, timeout, connection error | retryable | Retried after 1 s, then after 5 s (3 attempts in total). A Retry-After on 429 is honoured up to 30 s. |
| Any other 4xx, any 3xx, TLS error | rejected | Not retried (retrying would not help). Counts as three failures toward auto-disable. |
After the last failed attempt the batch is written to the error log and, in at-least-once mode, kept for resend. Each retry carries the same event id and a fresh timestamp and signature.
Delivery guarantees
- At least once (default): a failed batch is kept for 30 days and can be resent from Deliveries. Your endpoint may therefore see the same event twice: deduplicate by
id. - At most once: after the retries the batch is dropped. No duplicates, possible gaps.
- Frequency: by default every location FleetBold processes for a vehicle is forwarded (no sampling, no data loss). Optionally set "at most every N seconds" per webhook (minimum 5 s): a vehicle that reports faster is then sampled, not queued. Either way a vehicle that stops reporting sends nothing.
- Ordering: events of one vehicle are sent in the order they were produced. Across vehicles there is no ordering.
- Pause: pausing the webhook stops sending; events produced while paused are not queued.
Auto-disable
A webhook whose endpoint keeps failing is switched off automatically, so a dead endpoint does not accumulate failures forever. Any of these trips it:
- 50 consecutive failed batches and at least 30 minutes since the first failure.
- 5 consecutive hard rejections (for example 401, 403, 404, 422).
- 500 failures within 24 hours.
- 24 hours without a single successful delivery.
The webhook then shows
Field mapping
By default the envelope is sent as-is. Turn off "Send as-is" in the Payload step to build the shape your endpoint expects: pick a source path (or a constant value) per target field, add transforms, and watch the preview. Presets: ABI report_location, GeoJSON Feature, GPS position and Flat (spreadsheet / Zapier).
{
"version": 1,
"mode": "replace",
"root": null,
"omit_null": true,
"fields": [
{ "target": "device", "source": "vehicle.vin" },
{ "target": "lat", "source": "data.lat", "transforms": ["round:5"] },
{ "target": "lng", "source": "data.lon", "transforms": ["round:5"] },
{ "target": "speed_kmh", "source": "data.speed_mph", "transforms": ["unit:mph->kmh", "round:1"] },
{ "target": "at", "source": "occurred_at", "transforms": ["datetime:unix_ms"] },
{ "target": "provider", "value": "fleetbold" }
]
}{
"device": "1FT7W2BT8HEB21938",
"lat": 25.667,
"lng": -80.398,
"speed_kmh": 42.0,
"at": 1790161120000,
"provider": "fleetbold"
}| Setting | Meaning |
|---|---|
mode | replace sends only your fields; merge sends the envelope plus your fields. |
root | Optional key that wraps the output, for example "root": "events" → {"events": [ ... ]}. |
omit_null | Leave out targets whose source is missing on this event. |
fields[].target | Destination key; dots create nested objects (position.lat). |
fields[].source / value | A path into the envelope (data.lat, vehicle.vin, …) or a constant. |
fields[].transforms | Applied in order: round:N, to_int, to_float, to_string, abs, scale:F, unit:kmh->mph|mph->kmh|km->mi|mi->km|m->ft|ft->m|l->gal|gal->l|kpa->psi|psi->kpa, temp:c->f|f->c, datetime:iso8601_z|iso8601_offset|unix_s|unix_ms|strftime:<fmt>|tz:<Zone>, upper, lower, trim, prefix:X, suffix:X, slice:a:b, to_bool, bool_to_int, not, geojson_point, latlon_string:",". |
Security
- Endpoints must be HTTPS with a valid certificate. Hostnames that resolve to private, loopback, link-local or cloud-metadata addresses are refused, both when you save and on every send.
- Custom headers cannot override
Authorization,HostorContent-Type. - Tokens, passwords and signing secrets are encrypted at rest, shown once, and never returned by the API again.
- Authenticate requests by verifying the signature, not by source IP.
- Only members with the Data Forwarding permission can create or change webhooks; only vehicles active in the organization are forwarded.
Test event
"Send test event" on the webhook card pushes one synthetic vehicle.location through the same path as real traffic, signed the same way, with a fake VIN and "test": true at the top level and inside data. No vehicle needs to be assigned. Ignore test events in production processing.
{
"id": "evt_...",
"type": "vehicle.location",
"version": "fleetbold.v1",
"occurred_at": "2026-09-22T22:58:40Z",
"sent_at": "2026-09-22T22:58:40Z",
"vehicle": { "id": 0, "vin": "FLEETBOLDTEST0001", "nickname": "Test vehicle" },
"data": { "lat": 25.7617, "lon": -80.1918, "heading": 90, "speed_mph": 0, "speed_kmh": 0, "ignition": false, "test": true },
"source": { "family": "test" },
"test": true
}Limits (beta)
- One webhook per organization.
- One event per request (no batching).
- Event type:
vehicle.locationonly. - Timeout 1–30 s; optional interval minimum 5 s.
- Coming later: more event types, batching, other transport protocols (MQTT, Kafka, cloud queues) and API keys for pulling data.
Troubleshooting
| Symptom | Check |
|---|---|
| Nothing arrives | Is the webhook Active? Are vehicles assigned, and do they have a tracker or built-in tracking? Does the vehicle currently report? Deliveries on the card shows the last errors. |
| Test fails with "blocked url" | The URL is not HTTPS or resolves to a private / local address. Public HTTPS endpoints only. |
| TLS error | Your certificate chain is invalid or self-signed. |
| HTTP 401 / 403 in Deliveries | Your endpoint rejects the token or signature. Compare with the samples above; remember the raw body and the v1= prefix. |
| Timeouts | Respond before processing, or raise the timeout (max 30 s). |
| Duplicates | Expected with at-least-once after a retry or a resend. Deduplicate by id. |
| Disabled after failures | Fix the endpoint, then Re-enable from the card; the live test must pass. |
