Lydira API
TR

Guides

Webhooks

Three events, every booking type, one signature to check. Register an endpoint and stop polling.

Subscribing

A subscription narrows on two axes. events picks which of the three you receive, entities picks which record types. Leave either out and you get all of them, which also means a record type added later starts arriving at your endpoint. Pick explicitly if that matters to you.

events

  • record.created
  • record.updated
  • record.deleted

entities

  • car_rentals
  • charters
  • cruises
  • customers
  • events
  • hotel_stays
  • leads
  • operator_invoices
  • supplier_bills
  • suppliers
  • tickets
  • tour_bookings
  • tour_departures
  • transfers
  • visa_cases
curl -X POST https://YOUR-CELL.lydira.com/api/v1/webhooks \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "url": "https://example.com/hooks/lydira",
  "events": ["record.created", "record.updated"],
  "entities": ["hotel_stays", "tickets"]
}'

Broadcasting is not the same permission as importing

These types broadcast and can be read back, but cannot be pushed: a POST to one answers 422 entity_not_writable. Subscribing to tour_departures is also the only way to hear that a departure was cancelled or sold out, because that does not move the status of the seats already sold on it.
  • charters
  • events
  • visa_cases

The payload

Every event carries the same four keys. data is the record in the shape GET /api/v1/imports/{entity}/records/{external_id} returns, including your external_id when the record came in through an import. record.updated fires when a meaningful field moves, not on every recalculation.
POST your endpoint
{
  "id": "evt_8f14e45fea2c4f339a3b",
  "event": "record.updated",
  "occurred_at": "2026-03-14T09:30:00Z",
  "data": {
    "entity": "hotel_stays",
    "external_id": "HOTEL-STAY-1001",
    "status": "confirmed"
  }
}
Headers on every delivery
X-Lydira-Event:
X-Lydira-Delivery:
X-Lydira-Attempt:
X-Lydira-Signature:

Verifying the signature

Four steps. Parse t and v1 out of X-Lydira-Signature, take the SHA-256 hex digest of the whsec_ secret you stored at registration, compute HMAC-SHA256 over ., and compare against v1 in constant time. Reject an old t as well, or a captured delivery can be replayed at you later.
verify.js
import crypto from "node:crypto";

const SECRET = process.env.LYDIRA_WEBHOOK_SECRET;      // whsec_…
const signingKey = crypto.createHash("sha256").update(SECRET).digest("hex");

export function verify(rawBody, header) {
  const parts = Object.fromEntries(
    header.split(",").map((pair) => pair.split("=")),
  );

  // Reject anything older than five minutes: without this, a delivery
  // captured once can be replayed at you for ever.
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || age > 300) return false;

  const expected = crypto
    .createHmac("sha256", signingKey)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1 ?? ""),
  );
}

Delivery

Attempts
5, with exponential backoff
Success
any 2xx; anything else is retried
Timeouts
10s to connect, 15s to read
Destinations
public addresses only; private, loopback and link-local URLs are refused