FAFuelAtlasDevelopers

Developers / Guide

Webhooks

Register endpoints, subscribe to events, verify HMAC signatures, and handle retries and replays.

Webhooks push events to your server the moment they happen — a vehicle arrives at a stop, guidance refreshes, a fueling is detected — so you don't poll. Every delivery is signed so you can verify it came from FuelAtlas and wasn't tampered with.

Register an endpoint

HTTP
POST /v1/webhooks
Authorization: Bearer fa_live_…
Content-Type: application/json

Requires webhooks:manage.

JSON
{
  "url": "https://hooks.yourapp.com/fuelatlas",
  "event_types": ["guidance.updated", "vehicle.fuel_low", "fueling.matched"],
  "description": "Prod dispatch integration"
}

The URL must be HTTPS and publicly reachable — plain http and internal/loopback addresses are rejected at creation. event_types must be a subset of the catalog, or the single wildcard ["*"] to receive everything.

The response returns the endpoint plus a signing secret, shown exactly once:

JSON
{
  "id": "whk_4mZ…",
  "url": "https://hooks.yourapp.com/fuelatlas",
  "event_types": ["guidance.updated", "vehicle.fuel_low", "fueling.matched"],
  "status": "active",
  "secret": "whsec_9c…store_this_now"
}

Store secret immediately — you need it to verify deliveries, and it is never shown again. If you lose it, rotate it.

The delivery envelope

Each delivery is a POST to your URL with a JSON body and these headers:

POST /fuelatlas HTTP/1.1
Content-Type: application/json
User-Agent: FuelAtlas-Webhooks/1.0
X-FuelAtlas-Event-Id: evt_2s…
X-FuelAtlas-Event-Type: guidance.updated
X-FuelAtlas-Signature: t=1751813045,v1=6f2b…hex

sequence is a monotonically increasing integer per fleet_id — retries and backoff mean deliveries can arrive out of order, so use sequence (together with id for de-duplication) to detect gaps and reconstruct the correct order, rather than relying on receipt order.

Respond with any 2xx within a few seconds to acknowledge. Anything else (or a timeout) is treated as a failure and retried.

Verifying the signature

The X-FuelAtlas-Signature header is t=<unix-timestamp>,v1=<hex>. To verify: recompute HMAC-SHA256(secret, "{t}.{rawBody}") over the raw request body and constant-time-compare it to the v1 value. Bind the timestamp into a tolerance window (e.g. 5 minutes) to reject replays.

During a secret rotation the header may carry multiple v1= values — one per currently-valid secret. Accept the delivery if any of them matches.

import crypto from "node:crypto";

// rawBody: the exact bytes received (do NOT re-serialize the parsed JSON)
export function verify(rawBody: string, header: string, secret: string, toleranceSec = 300): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  // header may list several v1= values during rotation — match any
  return header
    .split(",")
    .filter((p) => p.startsWith("v1="))
    .some((p) => {
      const sig = p.slice(3);
      return sig.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
    });
}

Verify against the raw request body, before any JSON parsing re-serializes it. Re-encoding the parsed object will change the bytes and break the signature.

Event catalog

GET /v1/webhooks/event-types returns this list at runtime (any valid key, no scope). Subscribe to ["*"] to receive all of them.

EventFires when
guidance.updatedThe recommended fuel-stop set for a vehicle was created, refreshed, or cleared.
guidance.stop_approachingA vehicle came within the proximity radius of a recommended fuel stop.
vehicle.stop_arrivedGPS detected a vehicle arriving at a stop.
vehicle.stop_departedGPS detected a vehicle departing a stop.
trip.startedA dispatch trip transitioned to active.
trip.completedA dispatch trip completed.
trip.cancelledA dispatch trip was cancelled.
fueling.recordedA fueling was detected from a tank-level rise.
fueling.matchedA detected fueling was matched to a recommended or proposed stop.
vehicle.fuel_lowA vehicle's fuel percentage crossed the low-fuel threshold (with hysteresis).
vehicle.offlineA previously-reporting vehicle stopped sending location updates.
vehicle.onlineA vehicle that had gone offline resumed reporting.
fleet.provisionedA fleet was created via the provisioning API.

Sandbox endpoints (registered with a fa_test_ key) only receive events flagged sandbox: true; production endpoints only receive live events. The two never cross.

Retries and replays

A delivery that doesn't get a 2xx is retried on a backoff schedule over roughly a day. Deliveries are logged; inspect them with:

cURL
curl "https://api.fuelatlas.com/v1/webhooks/whk_4mZ…/deliveries?status=failed" \
  -H "Authorization: Bearer fa_live_…"

To re-send a specific past event (for example after fixing a bug on your side), replay it:

cURL
curl -X POST \
  https://api.fuelatlas.com/v1/webhooks/whk_4mZ…/deliveries/whd_8p…/replay \
  -H "Authorization: Bearer fa_live_…"

A replay re-delivers the same event (marked is_replay: true); it returns 410 event_expired if the event is past its replay window. Design your handler to be idempotent on X-FuelAtlas-Event-Id — replays and rare duplicate deliveries both repeat that id.

Test a delivery

Send a canned sample payload for any event type to your endpoint, synchronously, without waiting for the real event. A test send is signed like any delivery and never disables the endpoint:

cURL
curl -X POST https://api.fuelatlas.com/v1/webhooks/whk_4mZ…/test \
  -H "Authorization: Bearer fa_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "guidance.updated" }'

Rotating the secret

cURL
curl -X POST https://api.fuelatlas.com/v1/webhooks/whk_4mZ…/rotate-secret \
  -H "Authorization: Bearer fa_live_…"

Returns a new secret once. The previous secret stays valid for 24 hours (deliveries in that window are signed with both, so a verifier matching either accepts them). Roll the new secret into your config before the window closes.

Next

  • Autoguide — the source of guidance.* events.
  • Errors — the problem+json envelope every 4xx/5xx uses.