Ruby SDK
The official FlightByte gem for Ruby 3.1+. Look a flight up, watch it, and verify signed flight status webhooks with a ready Rack receiver for Rails, Sinatra and Roda, on the standard library alone.
flightbyte on RubyGems is the official Ruby client for the FlightByte flight status API. It looks flights up, watches them so every gate change, delay, departure and landing arrives as a signed webhook, and gives you a ready Rack app that verifies each delivery and mounts in Rails, Sinatra, Hanami or Roda. It uses only the standard library and runs on Ruby 3.1 to 4.0.
Source: flightbyte-ruby on GitHub, MIT license. Gem: flightbyte on RubyGems.
Install
bundle add flightbyteOr gem install flightbyte outside a Gemfile. In a Gemfile: gem "flightbyte", "~> 1.0". You need an API key: the free plan covers three watched flights a month, no card.
Look a flight up
require "flightbyte"
client = FlightByte::Client.new(api_key: ENV.fetch("FLIGHTBYTE_API_KEY"))
flight = client.flights.get(
carrier: "TK",
flight_number: "1848",
date: "2026-09-08" # local departure date at the origin
)
puts flight.status.label # Scheduled
puts flight.departure.gate # A11
puts flight.departure.delay_minutes # 15Every field reads as a snake_case method, flight["departure"]["delayMinutes"] reads it by the API's own key, and flight.to_h is the JSON exactly as received. Responses are frozen objects such as FlightByte::FlightSnapshot, and they match patterns: in { status: { phase: "landed" } }. date: takes a Date too. A number that flies more than one leg that day lists every leg in alternates: pass origin: "IST" to pick one. The full snapshot is under Flights.
Watch a flight
created = client.subscriptions.create(
carrier: "TK",
flight_number: "1848",
date: "2026-09-08",
webhook_url: "https://api.example.com/hooks/flights",
events: %w[departure.gate.changed delay.increased flight.departed],
metadata: { bookingId: "BK-1042" },
idempotency_key: "BK-1042" # a retry never counts twice
)
# Shown in full only now: store it where your webhook receiver can read it.
signing_secret = created.secretA subscription is one watched flight on your plan, however many changes it sends. Ten travellers on the same flight are one watched flight when you subscribe once and pass each change on to all ten. FlightByte delivers to public HTTPS addresses only: on your machine, expose the port with cloudflared tunnel --url http://localhost:3000 or ngrok http 3000 and use that URL. client.subscriptions.send_test(id) sends a signed subscription.test whenever you want to see the path work.
The other calls: list, list_all (every page), get, update, pause, resume, delete, deliveries, events, secret and rotate_secret. Every method takes a timeout: of its own.
Receive webhooks
A receiver verifies the signature against the raw body, skips an event id it has seen, answers 2xx within ten seconds and does the work afterwards. FlightByte::Webhook::RackApp does the first and answers for you: 401 for a bad signature, 400 for a body that is not an event, 500 when your block raises, so FlightByte retries, and 200 otherwise. It needs no Rack gem, so it mounts in every Ruby web framework.
FLIGHTBYTE_RECEIVER = FlightByte::Webhook::RackApp.new(ENV.fetch("FLIGHTBYTE_WEBHOOK_SECRET")) do |event|
next unless mark_handled(event.id) # a retry carries the same id
FlightEventJob.perform_later(event.to_h) # answer first, work afterwards
endBuild the receiver once, in an initializer, and mount it:
# config/routes.rb
mount FLIGHTBYTE_RECEIVER, at: "/hooks/flightbyte"In a Rails controller
Read the body with request.raw_post, not params. The signature covers the bytes as sent, and JSON parsed and serialised again no longer matches.
class FlightbyteWebhooksController < ActionController::API
def create
event = FlightByte::Webhook.construct_event(
request.raw_post,
request.headers[FlightByte::Webhook::SIGNATURE_HEADER],
ENV.fetch("FLIGHTBYTE_WEBHOOK_SECRET")
)
rescue FlightByte::WebhookSignatureError
head :unauthorized
rescue FlightByte::Error
head :bad_request
else
FlightEventJob.perform_later(event.to_h)
head :ok
end
end
class FlightEventJob < ApplicationJob
def perform(payload)
event = FlightByte::WebhookEvent.new(payload)
case event.type
when "flight.updated"
event.changes.each { |change| logger.info("#{event.flight_key} #{change.type}: #{change.message}") }
when "subscription.completed"
logger.info("#{event.flight_key} finished")
end
end
endIn a Sinatra route, the raw body is request.body.read and the header is request.env[FlightByte::Webhook::SIGNATURE_ENV_KEY].
An empty secret raises an ArgumentError at once, because anyone can sign with an empty key: an unset environment variable fails at boot instead of accepting forged events. The check is HMAC-SHA256 over "<t>." + raw body, compared with OpenSSL.fixed_length_secure_compare, with a timestamp more than five minutes off rejected. Verifying webhook signatures explains each step, and Webhooks has the payload and the 23 change types.
Errors
Every failed request raises a subclass of FlightByte::Error, so you rescue the ones you can handle and let the rest through.
| Error | Status | What to do |
|---|---|---|
BadRequestError | 400 | Fix the input; messages has a line per field. For a number that flies twice, legs lists them: pass one's origin. |
AuthenticationError | 401 | The key is missing, invalid, revoked or expired. |
PaymentRequiredError | 402 | No watched flights left. code is watched_flight_limit, overage_cap_reached (see resets_at) or credits_exhausted (see balance and expires_at). |
PermissionDeniedError | 403 | The account is suspended, or a token lacks the scope. |
NotFoundError | 404 | The flight did not operate that day, or there is no such subscription. |
RateLimitError | 429 | A minute, day or month quota is used up. Wait retry_after seconds. |
UpstreamError | 502 | The flight data provider failed. Retry shortly. |
ServerError, UnexpectedStatusError | other | Quote request_id to support. |
ConnectionError | none | No answer at all: refused connection, DNS, TLS or a dropped socket, or timed_out?. |
begin
client.subscriptions.create(**params)
rescue FlightByte::BadRequestError => e
raise if e.legs.empty?
puts "Pick a leg: #{e.legs.map(&:origin).join(", ")}"
rescue FlightByte::PaymentRequiredError => e
puts "No watched flights left (#{e.code}) until #{e.resets_at}"
rescue FlightByte::RateLimitError => e
sleep(e.retry_after || 5)
retry
endclient.last_rate_limit holds the minute, day and month windows from the latest response. Every status and code is in Errors.
Configuration
| Option | Default | Meaning |
|---|---|---|
api_key | required | Your API key, sent as x-api-key. |
base_url | https://api.flightbyte.dev/v1 | Another API base URL, such as a local one. |
timeout | 30 | Seconds each request may take, redirects and body included. 0 disables it. |
user_agent | none | Put in front of flightbyte-ruby/<version> in the user-agent header. |
Requests go through Net::HTTP, so HTTPS_PROXY, HTTP_PROXY and NO_PROXY apply.
Built for Ruby apps
- One client, many threads. Share it between Puma threads and Sidekiq workers; create it once, in an initializer. Responses are frozen, so they can be cached and handed between threads.
- No dependencies. Only
net/http,opensslandjson. - Secrets stay out of your logs.
inspectnever shows the key and masks a signing secret. - New values do not break old code. A phase, status or change type added later is still a plain string, and a new field reads as a method like the others. A misspelt field raises
NoMethodErrorinstead of passing for a missing value. - Zero is not unknown. A field the API left out reads as
nil, so a delay of0and no reading differ, andkey?tells the two apart.
Next
- Webhooks: payload, change types and the retry schedule.
- Missed connection alerts, built on two watched flights.
- FlightByte for airport transfers: pickups that move with the flight.
- The same calls in Node.js, Python, Go and Dart, or all five side by side.
Go SDK
The official FlightByte module for Go 1.23+. Look a flight up, watch it, and verify signed flight status webhooks with a ready http.Handler, on the standard library alone.
Dart SDK
The official FlightByte package for Dart 3.5+. Look a flight up, watch it, and verify signed flight status webhooks on a Dart server, in a CLI, or in the backend behind your Flutter app.