Travlix Developers

Travlix Travel API v1

Base URL https://api.travlix.com. Downloads: OpenAPI 3 (YAML) · Postman collection

Quick start

  1. Create an account and verify your email.
  2. Create a sandbox key in API keys and copy the secret (shown once).
  3. Sign each request: X-Signature = hex(HMAC-SHA256(secret, METHOD\nPATH\nQUERY\nTIMESTAMP\nNONCE\nhex(sha256(body)))), with X-Api-Key, X-Timestamp and X-Nonce.
  4. Flow: search → fare-quote → book (with Idempotency-Key) → tickets issued → webhook ticket.issued.

Signing samples

curl · download
#!/usr/bin/env bash
# Travlix API v1 - signed request with curl + openssl. Usage: TLX_KEY=... TLX_SECRET=... ./sign.sh
set -euo pipefail
BASE="${TLX_BASE:-https://api.travlix.com}"
tlx_call() { # method path [json-body]
  local method="$1" path="$2" body="${3:-}" ts nonce bh sig q p
  ts=$(date +%s); nonce=$(openssl rand -hex 16)
  p="${path%%\?*}"; q=""; [[ "$path" == *\?* ]] && q="${path#*\?}"
  bh=$(printf '%s' "$body" | openssl dgst -sha256 -hex | sed 's/^.* //')
  sig=$(printf '%s\n%s\n%s\n%s\n%s\n%s' "$method" "$p" "$q" "$ts" "$nonce" "$bh" | openssl dgst -sha256 -hmac "$TLX_SECRET" -hex | sed 's/^.* //')
  local args=(-sS -X "$method" "$BASE$path" -H 'Content-Type: application/json' -H "X-Api-Key: $TLX_KEY"
    -H "X-Timestamp: $ts" -H "X-Nonce: $nonce" -H "X-Signature: $sig")
  [ -n "$body" ] && args+=(--data-binary "$body")
  curl "${args[@]}"; echo
}
tlx_call GET /v1/wallet/balance
tlx_call POST /v1/flight/search "{\"origin\":\"DEL\",\"destination\":\"BOM\",\"departure_date\":\"$(date -d '+30 days' +%F 2>/dev/null || date -v+30d +%F)\",\"adults\":1}"
PHP · download
<?php
// Travlix API v1 - signed request in PHP (curl). Usage: TLX_KEY=... TLX_SECRET=... php sign.php
$base = getenv('TLX_BASE') ?: 'https://api.travlix.com';
$key = getenv('TLX_KEY');
$secret = getenv('TLX_SECRET');

function tlx_call(string $method, string $path, ?array $body = null, array $headers = []): array
{
    global $base, $key, $secret;
    $raw = $body === null ? '' : json_encode($body, JSON_UNESCAPED_SLASHES);
    $u = parse_url($path);
    $ts = (string) time();
    $nonce = bin2hex(random_bytes(16));
    $sts = implode("\n", [strtoupper($method), $u['path'], $u['query'] ?? '', $ts, $nonce, hash('sha256', $raw)]);
    $h = array_merge(['Content-Type: application/json', 'X-Api-Key: ' . $key, 'X-Timestamp: ' . $ts,
        'X-Nonce: ' . $nonce, 'X-Signature: ' . hash_hmac('sha256', $sts, $secret)], $headers);
    $ch = curl_init($base . $path);
    curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $h, CURLOPT_TIMEOUT => 90]);
    if ($method !== 'GET') curl_setopt($ch, CURLOPT_POSTFIELDS, $raw);
    $res = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [$code, json_decode((string) $res, true)];
}

[$code, $r] = tlx_call('GET', '/v1/wallet/balance');
echo "balance $code: " . json_encode($r['data'] ?? $r['error']) . "\n";
[$code, $r] = tlx_call('POST', '/v1/flight/search', ['origin' => 'DEL', 'destination' => 'BOM',
    'departure_date' => date('Y-m-d', strtotime('+30 days')), 'adults' => 1]);
echo "search $code: " . ($r['data']['count'] ?? 0) . " offers\n";
if (!empty($r['data']['offers'])) {
    [$code, $q] = tlx_call('POST', '/v1/flight/fare-quote', ['offer_id' => $r['data']['offers'][0]['offer_id']]);
    echo "fare-quote $code: total " . ($q['data']['price']['total'] ?? '?') . "\n";
    // Book (uncomment): tlx_call('POST', '/v1/flight/book', [...], ['Idempotency-Key: ' . bin2hex(random_bytes(12))]);
}
Python · download
"""Travlix API v1 - signed request in Python 3 (requests). Usage: TLX_KEY=... TLX_SECRET=... python3 sign.py"""
import datetime, hashlib, hmac, json, os, secrets, time
from urllib.parse import urlsplit
import requests

BASE = os.environ.get("TLX_BASE", "https://api.travlix.com")
KEY, SECRET = os.environ["TLX_KEY"], os.environ["TLX_SECRET"]


def tlx_call(method, path, body=None, headers=None):
    raw = b"" if body is None else json.dumps(body, separators=(",", ":")).encode()
    u = urlsplit(path)
    ts, nonce = str(int(time.time())), secrets.token_hex(16)
    sts = "\n".join([method.upper(), u.path, u.query, ts, nonce, hashlib.sha256(raw).hexdigest()])
    sig = hmac.new(SECRET.encode(), sts.encode(), hashlib.sha256).hexdigest()
    h = {"Content-Type": "application/json", "X-Api-Key": KEY, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sig}
    h.update(headers or {})
    r = requests.request(method, BASE + path, data=raw if method != "GET" else None, headers=h, timeout=90)
    return r.status_code, r.json()


if __name__ == "__main__":
    print("balance", *tlx_call("GET", "/v1/wallet/balance"))
    date = (datetime.date.today() + datetime.timedelta(days=30)).isoformat()
    code, r = tlx_call("POST", "/v1/flight/search", {"origin": "DEL", "destination": "BOM", "departure_date": date, "adults": 1})
    print("search", code, r.get("data", {}).get("count"), "offers")
    if r.get("data", {}).get("offers"):
        code, q = tlx_call("POST", "/v1/flight/fare-quote", {"offer_id": r["data"]["offers"][0]["offer_id"]})
        print("fare-quote", code, q.get("data", {}).get("price", {}).get("total"))
Node.js · download
// Travlix API v1 - signed request in Node.js 18+ (built-in fetch). Usage: TLX_KEY=... TLX_SECRET=... node sign.js
const crypto = require('crypto');
const BASE = process.env.TLX_BASE || 'https://api.travlix.com';
const KEY = process.env.TLX_KEY, SECRET = process.env.TLX_SECRET;

async function tlxCall(method, path, body, headers = {}) {
  const raw = body === undefined ? '' : JSON.stringify(body);
  const u = new URL(path, BASE);
  const ts = String(Math.floor(Date.now() / 1000));
  const nonce = crypto.randomBytes(16).toString('hex');
  const sts = [method.toUpperCase(), u.pathname, u.search.replace(/^\?/, ''), ts, nonce,
    crypto.createHash('sha256').update(raw).digest('hex')].join('\n');
  const sig = crypto.createHmac('sha256', SECRET).update(sts).digest('hex');
  const res = await fetch(u, { method, body: method === 'GET' ? undefined : raw, headers: {
    'Content-Type': 'application/json', 'X-Api-Key': KEY, 'X-Timestamp': ts, 'X-Nonce': nonce, 'X-Signature': sig, ...headers } });
  return [res.status, await res.json()];
}

(async () => {
  console.log('balance', ...(await tlxCall('GET', '/v1/wallet/balance')));
  const date = new Date(Date.now() + 30 * 864e5).toISOString().slice(0, 10);
  const [code, r] = await tlxCall('POST', '/v1/flight/search', { origin: 'DEL', destination: 'BOM', departure_date: date, adults: 1 });
  console.log('search', code, r.data && r.data.count, 'offers');
  if (r.data && r.data.offers.length) {
    const [c2, q] = await tlxCall('POST', '/v1/flight/fare-quote', { offer_id: r.data.offers[0].offer_id });
    console.log('fare-quote', c2, q.data && q.data.price.total);
  }
})();

Verifying webhooks

Header X-Travlix-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(webhook secret, t + "." + raw body). Reject if older than 5 minutes; de-duplicate on X-Travlix-Delivery.

PHP
<?php
// Verify a Travlix webhook (plain PHP endpoint). Set TLX_WEBHOOK_SECRET.
$secret = getenv('TLX_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$hdr = $_SERVER['HTTP_X_TRAVLIX_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $hdr), $p);
$t = (int) ($p['t'] ?? 0);
$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!$t || abs(time() - $t) > 300 || !hash_equals($expected, (string) ($p['v1'] ?? ''))) {
    http_response_code(400);
    exit('bad signature');
}
$event = json_decode($raw, true);
// De-duplicate on $event['id'] (also in X-Travlix-Delivery), then handle $event['type'] / $event['data'].
http_response_code(200);
echo 'ok';
Python (Flask)
"""Verify a Travlix webhook (Flask). Set TLX_WEBHOOK_SECRET."""
import hashlib, hmac, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["TLX_WEBHOOK_SECRET"].encode()


@app.post("/travlix/webhook")
def hook():
    raw = request.get_data()
    parts = dict(p.split("=", 1) for p in request.headers.get("X-Travlix-Signature", "").split(",") if "=" in p)
    t = parts.get("t", "")
    expected = hmac.new(SECRET, t.encode() + b"." + raw, hashlib.sha256).hexdigest()
    if not t.isdigit() or abs(time.time() - int(t)) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(400)
    event = request.get_json()
    # de-duplicate on event["id"], then handle event["type"]
    return "ok"
Node.js (Express)
// Verify a Travlix webhook (Express). Set TLX_WEBHOOK_SECRET. Use the RAW body, not re-serialized JSON.
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/travlix/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const parts = Object.fromEntries((req.get('X-Travlix-Signature') || '').split(',').map((p) => p.split('=')));
  const t = parts.t || '';
  const expected = crypto.createHmac('sha256', process.env.TLX_WEBHOOK_SECRET).update(t + '.' + req.body.toString('utf8')).digest('hex');
  const ok = /^\d+$/.test(t) && Math.abs(Date.now() / 1000 - Number(t)) <= 300 && parts.v1 && parts.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  if (!ok) return res.status(400).send('bad signature');
  const event = JSON.parse(req.body.toString('utf8'));
  // de-duplicate on event.id, then handle event.type
  res.send('ok');
});
app.listen(3000);

API reference