Guide to WeTravel Webhooks: Features and Best Practices
WeTravel webhooks tell tour operators and their software partners when something happens on WeTravel: a traveller books, a payment succeeds or fails, money moves, a trip is published, or a prospective traveller downloads a brochure. If you run your CRM, accounting or operations outside WeTravel, webhooks keep them up to date without polling.
This guide covers how to switch WeTravel webhooks on, the nine event types and what triggers them, how to verify deliveries, and the best practices for running a WeTravel receiver in production. WeTravel's public documentation is short, so this guide also points out what it leaves unsaid.
What are WeTravel webhooks?
WeTravel offers webhooks to enterprise clients and software partners. They are disabled by default. Once enabled, you manage endpoints from a Manage Webhooks window, where each endpoint can listen to specific event types or to all of them, and where the Event Catalog lists every event with its structure and examples.
WeTravel doesn't publish the payload schemas, the signing scheme or the retry policy in its public docs. Hookdeck's WeTravel source type verifies deliveries as Svix messages, which is what the verification code in this guide follows.
WeTravel webhook features
| Feature | Details |
|---|---|
| Availability | Enterprise clients and software partners, disabled by default |
| Enabling | Profile, Edit Profile, Webhooks section (requires an API key) |
| Endpoint management | Manage Webhooks, then + Add Endpoint |
| Event filtering | Per endpoint: specific events or all events |
| Multiple endpoints | Supported |
| Payload reference | Event Catalog inside Manage Webhooks |
| Signature | Not documented by WeTravel. Hookdeck verifies it as a Svix signature |
| Retries and timeout | Not documented |
Common events
| Event | Fired when |
|---|---|
booking.created | A booking is made on a trip page or embedded widget, or added with Add Participant in Manage Trip |
booking.updated | A booking changes: departure date, refund, cancellation, a participant edit, custom price, package switch, bulk action, eSignature, new payment, failed or disputed payment, trip details, balance due, or a rebooking that cancels the original |
payment.created | A new payment is initiated |
payment.updated | A payment moves from pending to successful or failed, from successful to refunded or disputed, or to a new trip on rebooking |
transaction.created | A payment is received, or a refund, dispute, payout, instant payout, card transfer, vendor payment or wire transfer is initiated, or a top-up is received, or WeTravel adjusts the balance |
transaction.updated | A rebooking moves the transaction to a new trip |
trip.published | A trip is published in the trip builder or through the Trip Builder API |
lead.created | Someone downloads a brochure or asks a question on a trip or profile page |
lead.updated | The same lead downloads another brochure or asks another question |
booking.updated covers many different changes, so the event name alone won't tell you what happened. Compare the booking with your stored copy, or check the fields shown for it in the Event Catalog.
Setting up WeTravel webhooks
- Generate a WeTravel API key. The Webhooks section of your profile only appears once you have one.
- Go to Profile, then Edit Profile, find Webhooks and click Yes.
- Click Manage Webhooks, then + Add Endpoint. Enter your HTTPS URL and choose specific events or all events.
- Copy the endpoint's signing secret and store it server-side, for example as
WETRAVEL_WEBHOOK_SECRET. - Open the Event Catalog to see the structure of each event before writing your handler.
You can add several endpoints, and edit, delete or disable each one from the endpoint manager.
Securing WeTravel webhooks
WeTravel doesn't document how its webhooks are signed. Hookdeck verifies WeTravel deliveries with the Svix scheme: svix-id, svix-timestamp and svix-signature headers, and a base64 HMAC-SHA256 over {id}.{timestamp}.{raw body}, keyed with the base64-decoded part of a whsec_ secret. Check the headers on your first delivery to confirm the names before you rely on them.
const crypto = require('crypto');
const express = require('express');
function verifyWeTravelSignature(rawBody, headers, secret) {
const msg_id = headers['svix-id'];
const msg_timestamp = headers['svix-timestamp'];
const signature_header = headers['svix-signature'];
if (!msg_id || !msg_timestamp || !signature_header) return false;
if (typeof secret !== 'string' || !secret.startsWith('whsec_')) return false;
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const signed_content = Buffer.concat([Buffer.from(`${msg_id}.${msg_timestamp}.`, 'utf8'), rawBody]);
const expected = Buffer.from(
crypto.createHmac('sha256', key).update(signed_content).digest('base64'),
'utf8'
);
return signature_header.split(' ').some((entry) => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const received = Buffer.from(signature, 'utf8');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
const app = express();
app.post('/webhooks/wetravel', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.WETRAVEL_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyWeTravelSignature(req.body, req.headers, secret)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body.toString('utf8'));
// Deduplicate on the svix-id header, queue the event, then respond.
res.sendStatus(200);
});
The same check in Python:
import base64
import hashlib
import hmac
import os
def verify_wetravel_signature(raw_body: bytes, headers, secret: str) -> bool:
msg_id = headers.get("svix-id")
msg_timestamp = headers.get("svix-timestamp")
signature_header = headers.get("svix-signature")
if not (msg_id and msg_timestamp and signature_header):
return False
if not secret or not secret.startswith("whsec_"):
return False
key = base64.b64decode(secret[len("whsec_"):])
signed_content = f"{msg_id}.{msg_timestamp}.".encode("utf-8") + raw_body
expected = base64.b64encode(hmac.new(key, signed_content, hashlib.sha256).digest())
for entry in signature_header.split(" "):
version, _, signature = entry.partition(",")
if version == "v1" and hmac.compare_digest(signature.encode("utf-8"), expected):
return True
return False
# In FastAPI: raw_body = await request.body()
# verify_wetravel_signature(raw_body, request.headers, os.environ["WETRAVEL_WEBHOOK_SECRET"])
If your deliveries carry webhook-id, webhook-timestamp and webhook-signature instead, swap the header names. The scheme is the same.
Make WeTravel webhooks production-ready. Hookdeck Event Gateway verifies WeTravel deliveries, queues every booking, payment and transaction event, and keeps them available for replay.
Hookdeck's WeTravel source type verifies the signature with the secret you enter and accepts either svix- or webhook- header names. Verification is optional on the source, so leave it off if you didn't set a secret. Hookdeck does not apply a timestamp window.
WeTravel webhook limitations and pain points
Most of the reference is behind a login
The Problem: You can't design a handler from the public docs alone.
Why It Happens: WeTravel's public page lists event names and triggers. The payload structures and examples live in the Event Catalog inside Manage Webhooks, and signing, retries and timeouts aren't documented.
Workarounds:
- Read the Event Catalog before writing code, and keep a copy of the examples in your repository as test fixtures.
- Log the headers and body of your first real deliveries.
How Hookdeck Can Help: Hookdeck stores every request with its headers and body, so you can inspect real WeTravel events and replay them against your handler while you build it.
Broad update events
The Problem: A booking.updated arrives, and you don't know whether it was a refund, a date change or a new eSignature.
Why It Happens: WeTravel fires booking.updated for more than a dozen kinds of change.
Workarounds:
- Store the latest booking you have seen and diff it against the event.
- Listen to
payment.*andtransaction.*for money movements rather than inferring them frombooking.updated.
How Hookdeck Can Help: Hookdeck filters can route events by body fields, so payment and lead events can go to different services from booking events.
Unknown retry behaviour
The Problem: You can't tell how long WeTravel will keep trying after your endpoint goes down.
Why It Happens: WeTravel doesn't document retries or the response timeout.
Workarounds:
- Respond with a
2xxquickly and process in the background. - After an outage, reconcile bookings and payments through the WeTravel API.
How Hookdeck Can Help: Hookdeck accepts WeTravel's deliveries while your service is down and holds them until you replay them, whatever WeTravel's own retry policy is.
Best practices
Verify against the raw body
Use express.raw() in Express, Buffer.from(await request.arrayBuffer()) in a Next.js route handler, or await request.body() in FastAPI, and parse only after verification.
Subscribe only to what you use
Each endpoint can listen to specific events. Separate endpoints for bookings, payments and leads make each handler simpler.
Make handlers idempotent
Deduplicate on the delivery's message ID header and make updates safe to repeat. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Return a 2xx as soon as the event is stored. See why to process webhooks asynchronously.
Conclusion
WeTravel webhooks cover bookings, payments, transactions, trips and leads, and you switch them on from your profile once you have an API key. The public docs stop at event names and triggers, so use the Event Catalog for payloads, verify each delivery with your endpoint's whsec_ secret, and confirm the headers on your first real event.
Hookdeck Event Gateway verifies WeTravel deliveries and holds each event until your service has processed it.
Get started with Hookdeck for free and handle WeTravel webhooks reliably in minutes.