Waggish TravelsDevelopersInteractive API referenceopenapi v2openapi v1

Webhooks

Webhooks push events to your server so you don't have to poll.

Register an endpoint

Requires scope webhooks:manage. Up to 10 endpoints per client.

curl -X POST https://searchurtrip.com/api/v1/webhooks \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://partner.example.com/hooks/waggish","events":["booking.created","booking.cancelled"]}'
{
  "data": {
    "id": "whk_4",
    "url": "https://partner.example.com/hooks/waggish",
    "events": ["booking.created", "booking.cancelled"],
    "status": "active",
    "createdAt": "2026-09-26 02:20:38",
    "secret": "whsec_…"
  }
}

The secret is returned only once. Store it to verify signatures. An empty events array subscribes to everything. In production the URL must be public https; private, loopback and link-local hosts are rejected.

Events

Event Sent when
booking.created A booking is created through the API (live keys only)
booking.cancelled A booking is cancelled through the API
invoice.paid An invoice for your tenant becomes fully paid

Delivery format

POST to your URL with a JSON body:

{
  "id": "evt_9c1e0d5b7a3f2c4e6a",
  "type": "booking.created",
  "created": "2026-09-26T02:20:38.104Z",
  "livemode": true,
  "data": { "id": "bkg_42", "pnr": "J3P74T", "type": "flight", "status": "Upcoming", "amount": 27143, "currency": "INR", "reference": "PO-1001" }
}

Headers:

Header Value
X-Waggish-Event Event type
X-Waggish-Delivery Delivery id (use with id to de-duplicate)
X-Waggish-Signature t=<unix seconds>,v1=<hex hmac>
User-Agent Waggish-Webhooks/1.0

Respond with any 2xx within 5 seconds to acknowledge. Do the real work asynchronously.

Verifying the signature

Compute HMAC_SHA256(secret, "<t>.<raw body>") and compare it (constant-time) to v1. Reject if t is more than 5 minutes old.

Node.js (Express)

import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/hooks/waggish", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-Waggish-Signature") ?? "";
  const [, t, v1] = /t=(\d+),v1=([a-f0-9]+)/.exec(header) ?? [];
  const expected = crypto
    .createHmac("sha256", process.env.WAGGISH_WEBHOOK_SECRET)
    .update(`${t}.${req.body.toString("utf8")}`)
    .digest("hex");

  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  const valid = v1 && v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
  if (!fresh || !valid) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // …handle event, de-duplicating on event.id
  res.sendStatus(200);
});

Python (Flask)

import hashlib, hmac, os, re, time
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/hooks/waggish")
def hook():
    m = re.match(r"t=(\d+),v1=([a-f0-9]+)", request.headers.get("X-Waggish-Signature", ""))
    if not m: abort(400)
    t, v1 = m.groups()
    expected = hmac.new(os.environ["WAGGISH_WEBHOOK_SECRET"].encode(),
                        f"{t}.{request.get_data(as_text=True)}".encode(), hashlib.sha256).hexdigest()
    if abs(time.time() - int(t)) > 300 or not hmac.compare_digest(v1, expected): abort(400)
    event = request.get_json()
    return "", 200

Always verify against the raw body, before any JSON parsing or re-serialisation.

Retries

A non-2xx response, a timeout, or a connection error is retried with increasing delays: 1 min, 5 min, 30 min, 2 h, 12 h. After the final attempt the delivery is marked failed. Deliveries are at-least-once: de-duplicate on the event id. Delivery attempts and their status are visible in your console's API Access screen.

Managing endpoints

curl https://searchurtrip.com/api/v1/webhooks -H "Authorization: Bearer $KEY"            # list (secrets are never returned)
curl -X DELETE https://searchurtrip.com/api/v1/webhooks/whk_4 -H "Authorization: Bearer $KEY"