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

  • bus_bookings
  • car_rentals
  • charters
  • cruises
  • customers
  • events
  • hotel_stays
  • leads
  • operator_invoices
  • rail_bookings
  • 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.

    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