FlightByte

Webhooks

The delivery format, every event and change type, how to verify signatures, and what retries look like.

A webhook is a POST with a JSON body to your subscription's URL. It carries the event, the full flight snapshot at that moment, and the list of what changed since the previous delivery.

Headers

POST /hooks/flights HTTP/1.1
Content-Type: application/json
x-flightbyte-event-id: evt_2f0c8a4e-6f1d-4d0d-9a3b-0f1e2d3c4b5a
x-flightbyte-event-type: flight.updated
x-flightbyte-subscription-id: sub_7fd9fa92faf3475db99b
x-flightbyte-delivery-attempt: 1
x-flightbyte-timestamp: 1789305971
x-flightbyte-signature: t=1789305971,v1=5f1c9a...e3b2

Any headers you set on the subscription are sent as well.

Legacy header names

The same six values are also sent as x-flightradar-* for receivers written before the rename. Read the x-flightbyte-* names in new code; the old ones will be dropped in a future release.

Payload

{
  "id": "evt_2f0c8a4e-6f1d-4d0d-9a3b-0f1e2d3c4b5a",
  "type": "flight.updated",
  "createdAt": "2026-09-12T09:26:11.000Z",
  "subscriptionId": "sub_7fd9fa92faf3475db99b",
  "flightKey": "TK1848:2026-09-12@ATH-IST",
  "flight": { "...": "the full flight snapshot" },
  "changes": [
    {
      "type": "departure.gate.changed",
      "path": "departure.gate",
      "from": "A11",
      "to": "A22",
      "message": "Departure gate changed from A11 to A22"
    },
    {
      "type": "arrival.estimated.changed",
      "path": "arrival.times.estimatedGate",
      "from": "2026-09-12T10:45:00Z",
      "to": "2026-09-12T11:15:00Z",
      "deltaMinutes": 30,
      "message": "Arrival estimated gate time changed from 2026-09-12T10:45:00Z to 2026-09-12T11:15:00Z"
    }
  ],
  "previousFetchedAt": "2026-09-12T09:16:04.000Z",
  "metadata": { "bookingId": "BK-1042" }
}
FieldMeaning
idStable across retries. Deduplicate on it.
typeOne of the event types.
flightThe snapshot as of this event. Present on every event that has a resolved flight.
changesEmpty on lifecycle events; otherwise one entry per change, each with a path into the snapshot, from, to, a human message, and deltaMinutes for time changes.
previousFetchedAtWhen the snapshot these changes were computed against was taken.
metadataExactly what you stored on the subscription.

Event types

TypeWhen
subscription.createdRight after creation. Carries the flight if it could be resolved.
subscription.activatedA pending subscription's flight appeared upstream.
flight.updatedSomething about the flight changed. The only type with a non-empty changes.
subscription.testYou called the test endpoint.
subscription.suspendedDeliveries kept failing and polling stopped. Delivery of this one is attempted too.
subscription.completedThe watch ended: the flight arrived, or the date passed without it.

Change types

These are the values for a subscription's events filter and for changes[].type. A flight.updated event can carry several at once.

TypeFires when
status.changedThe status code or phase changed for any reason.
flight.departedThe flight left the gate or the runway.
flight.arrivedThe flight reached the gate.
flight.cancelledCancelled.
flight.divertedLanding somewhere other than planned.
flight.reinstatedA cancelled flight is back.
departure.gate.changed, departure.terminal.changedDeparture gate or terminal moved.
arrival.gate.changed, arrival.terminal.changedArrival gate or terminal moved.
arrival.baggage.changedBaggage carousel assigned or changed.
departure.scheduled.changed, departure.estimated.changed, departure.actual.changedA departure time changed.
arrival.scheduled.changed, arrival.estimated.changed, arrival.actual.changedAn arrival time changed.
delay.increased, delay.decreasedThe gate delay grew or shrank by at least delayThresholdMinutes.
aircraft.changed, aircraft.tail.changedAircraft type or the specific airframe changed.
route.changedOrigin or destination changed.
codeshares.changedThe codeshare list changed.

Verifying a delivery

The signature header is t=<unix seconds>,v1=<hex> where v1 is HMAC-SHA256(secret, "<t>.<raw body>"). Verify against the raw bytes you received, before any JSON parsing, and reject timestamps older than a few minutes; FlightByte itself allows five.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret: string, header: string, rawBody: Buffer, toleranceSeconds = 300): boolean {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
  if (!parts.t || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex");
  return parts.v1.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

Frameworks that parse JSON for you

Express, Fastify, Django and friends usually hand you an object, not bytes. Configure the route to keep the raw body (express.raw, rawBody: true in Fastify, request.body in Django) and verify that; re-serialising the parsed object will not reproduce the exact bytes that were signed.

Retries and suspension

A delivery succeeds when your endpoint answers any 2xx within ten seconds. Otherwise:

  • Network errors, timeouts and 5xx responses are retried up to 8 times with exponential backoff, starting at four seconds.
  • 408 and 429 are retried the same way.
  • Any other 4xx is treated as your endpoint rejecting the event and is not retried.
  • Every attempt is written to the delivery log with its status and latency.

After 20 consecutive failed deliveries the subscription is suspended and polling stops. Fix the endpoint, then POST /v1/subscriptions/{id}/resume; use the events log to see what you missed.

Writing a receiver that behaves

  • Answer fast, work later. Acknowledge with 204, then process. Ten seconds is generous until your database is slow.
  • Deduplicate on id. A retry carries the same id and the same body. Keep a small table of processed ids.
  • Do not assume order. Retries can arrive after a newer event. createdAt and flight.fetchedAt tell you which snapshot is newer; when in doubt, the flight in the latest event is the truth.
  • Use metadata, not the URL. One endpoint can serve every subscription; metadata tells you which booking it is about.
  • Fail loudly in development. Log signature failures with the reason; a wrong secret and a re-serialised body look identical from the outside.

Three receivers that do all of the above, in Node.js, Python and Go, with a script that sends signed test events, are in flightbyte-webhook-examples.

On this page