Webhook documentation

Beta

Everything 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

  1. 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.
  2. Send a test event from the webhook card menu. Your endpoint must answer 2xx.
  3. Assign vehicles (Vehicles on the card): only vehicles with a connected tracker or built-in tracking can report.
  4. 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).

Example request
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"}
HeaderValue
Content-Typeapplication/json
User-AgentFleetBold-DataForwarding/1
X-FleetBold-TimestampUnix time in seconds when the request was signed.
X-FleetBold-Signaturev1= followed by hex HMAC-SHA256 of timestamp + "." + body. Always present.
X-FleetBold-Event-IdThe event id (same as id in the body). Use it to deduplicate.
X-FleetBold-Stream-IdNumeric id of your webhook.
AuthorizationBearer <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.

vehicle.location · fleetbold.v1
{
  "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 }
}
FieldMeaning
idUnique event id (evt_…). Stable across retries of the same event.
type / versionvehicle.location / fleetbold.v1.
occurred_atWhen the device recorded the point (ISO-8601, UTC, Z).
sent_atWhen FleetBold sent this request.
organization_idThe organization the webhook belongs to.
vehicleid, vin, imei (tracker, when any), nickname, plate, make, model, year. Only what is known is present.
datalat, 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.
sourceWhich 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.

AuthenticationSigning secret
HMAC signing secretThe signing secret (recommended: Generate creates a whsec_… value shown once).
Bearer token / API key headerThe token.
Basic authThe password.
NoneA key managed by FleetBold that you do not hold: the header is present but you cannot verify it. Choose HMAC to verify.
Express
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 responseFleetBold treats it asWhat happens
2xxacceptedDone. Nothing is stored on our side.
408, 425, 429, 500, 502, 503, 504, timeout, connection errorretryableRetried 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 errorrejectedNot 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

Disabled after failures
, the organization owner and members who manage data forwarding receive an email and a push notification, and nothing is sent until someone re-enables it. Re-enabling runs a live test first: it succeeds only when your endpoint answers 2xx.

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).

Mapping
{
  "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" }
  ]
}
What your endpoint receives
{
  "device": "1FT7W2BT8HEB21938",
  "lat": 25.667,
  "lng": -80.398,
  "speed_kmh": 42.0,
  "at": 1790161120000,
  "provider": "fleetbold"
}
SettingMeaning
modereplace sends only your fields; merge sends the envelope plus your fields.
rootOptional key that wraps the output, for example "root": "events" → {"events": [ ... ]}.
omit_nullLeave out targets whose source is missing on this event.
fields[].targetDestination key; dots create nested objects (position.lat).
fields[].source / valueA path into the envelope (data.lat, vehicle.vin, …) or a constant.
fields[].transformsApplied 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, Host or Content-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.

Test event
{
  "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.location only.
  • 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

SymptomCheck
Nothing arrivesIs 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 errorYour certificate chain is invalid or self-signed.
HTTP 401 / 403 in DeliveriesYour endpoint rejects the token or signature. Compare with the samples above; remember the raw body and the v1= prefix.
TimeoutsRespond before processing, or raise the timeout (max 30 s).
DuplicatesExpected with at-least-once after a retry or a resend. Deduplicate by id.
Disabled after failuresFix the endpoint, then Re-enable from the card; the live test must pass.