← Back to home

Docs · Webhooks

Push, not poll.

DirtFleet webhooks deliver typed JSON events to your endpoint, signed with HMAC-SHA256. Fourteen event types today; the AsyncAPI 2.6 spec at /asyncapi.yaml documents the full payload surface so you can generate typed handlers in any language.

The contract in one paragraph

You register a subscription (a URL + a list of event types you care about) via POST /api/v1/webhooks. The response includes a signing secret returned once — store it. When a matching event fires, we POST a JSON envelope to your URL with three headers: X-Fleetgo-Signature (HMAC-SHA256 over ${timestamp}.${body}), X-Fleetgo-Timestamp (unix seconds), and X-Fleetgo-Event (the event type, for routing).

Receivers verify against the raw body (not the parsed JSON — re-serializing changes whitespace and breaks the MAC). Reject deliveries older than 5 minutes (replay window). Persist the envelope's id as an idempotency key in the same transaction as your side effect, so retries don't double-fire.

AsyncAPI spec

Generate typed handlers from /asyncapi.yaml

The full event surface — every channel, every payload shape — is documented in AsyncAPI 2.6 at /asyncapi.yaml. Same role for the push side as /openapi.yaml has for the pull side.

# AsyncAPI generator — Node CLI
npm i -g @asyncapi/cli
asyncapi generate models \
  typescript \
  https://dirtfleet.app/asyncapi.yaml \
  -o ./src/dirtfleet-events

# Then in your code
import { FlagCreated, WorkOrderCompleted } from "./dirtfleet-events";
function handle(env: { event: string; data: unknown }) {
  switch (env.event) {
    case "flag.created":  return onFlagCreated(env as FlagCreated);
    case "workorder.completed":
      return onWorkOrderCompleted(env as WorkOrderCompleted);
    /* ... */
  }
}

AsyncAPI generators also exist for Java, Python, Go, .NET — see the official tooling matrix at asyncapi.com/tools.

Event types

What you can subscribe to

  • flag.created — driver or anomaly raised a flag.
  • flag.resolved — a flag was cleared.
  • hours.logged — new meter reading or state-only entry.
  • asset.created — new asset row.
  • asset.updated — identity, plate, renewals, financials, or lifecycle changed. Payload includes a changedFields[] array for routing.
  • workorder.created / workorder.completed.
  • tool.failure — tool reported BROKEN via POST /api/v1/tools/{id}/report or a BROKEN check-in.
  • tool.checked_out — tool checked out to a user or vehicle. Payload includes toUserId, toVehicleId, optional linkedAssetId.
  • tool.checked_in — tool checked back in. Payload carries the observed condition + resulting newStatus + the auto-spawned workOrderId when condition is BROKEN.
  • tool.low_stock — a consumable just crossed from above-threshold to at-or-below-threshold via a POST /tools/{id}/adjust-stock call. Payload includes previousStockLevel, newStockLevel, the row-level minStockLevel (or null for the org-wide fallback), and the effective thresholdUsed. Only fires once per crossing — going further below or restocking back above does not retrigger.
  • tool.assignment_changed — a tool's owner moved (assignedUserId, assignedVehicleId, assignedYardId, or parentKitId changed via PATCH /tools/{id}). A single PATCH that touches multiple assigned fields produces one delivery whose changes array enumerates each kind that moved. Mirrors the internal ASSIGNMENT ToolEvent audit feed — wire this so accounting / HR systems don't have to poll /tools/{id}/events.
  • tool.serviced — a PM service was recorded via POST /tools/{id}/mark-serviced. Payload includes previousLastServicedAt (null on first service), newLastServicedAt, pmIntervalDays, the actorId, and any free-form note. Mirrors the internal pm_serviced: AUDIT ToolEvent — wire this to asset depreciation systems, OEM warranty trackers, or any CMMS that maintains a parallel service-history ledger.
  • tool.created — a new Tool was created via POST /tools or POST /tools/batch. Bulk imports emit one delivery per row. Payload includes toolId, name, category, isConsumable, scanToken, optional purchaseCost, and the actorId (null on bulk-import paths that don't attribute creation to a specific user). Mirror of asset.created — keep accounting / asset-register ledgers in lockstep without nightly reconciliation.

Each subscription opts into a CSV of events. Unknown event types in your subscription list are silently dropped at create time (forgiving on typos).

Retries + delivery history

What happens when your receiver is down

Failed deliveries — anything that wasn't a 2xx response — requeue with exponential backoff: 30s → 2m → 8m → 30m → 2h → 8h. After 6 attempts we mark the delivery ABANDONED and stop. The full history (response status, response body truncated to 500 chars, next-attempt time) is queryable via GET /api/v1/webhooks/{id}/deliveries with optional ?status=FAILED,ABANDONED to narrow.

Testing

Synthetic deliveries

POST /api/v1/webhooks/{id}/test fires a synthetic flag.created envelope at your URL with the live signature + an X-Fleetgo-Test: 1 header. We return the receiver's status and the first 500 bytes of its response body — useful for confirming your signature verification works without waiting for a real event.

Idempotency

The receiver pattern that actually works

A failed delivery will retry. If your receiver returns a 200 but crashes before persisting, we think it landed and stop retrying — but the side effect didn't happen. If your receiver returns a 500 after the side effect already happened, we retry and you double-fire. The fix for both is the same: persist the envelope's id to a unique-indexed processed_webhooks table inside the same transaction as your side effect. On retry, the insert collides on the unique index, your handler short-circuits to 200, no double-fire.

More on this in the idempotency-keys post (the same pattern works for both directions).

Working examples

Drop-in Node.js receiver

A complete Express receiver with signature verification + replay-window check + the idempotency pattern is on /docs/api/example — copy-paste into your codebase as the starting point.

← Back to API reference · REST OpenAPI spec → · SDK codegen →