# 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](/webhooks/platforms/guide-to-svix-webhooks-features-and-best-practices) 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

1. Generate a WeTravel API key. The **Webhooks** section of your profile only appears once you have one.
2. Go to **Profile**, then **Edit Profile**, find **Webhooks** and click **Yes**.
3. Click **Manage Webhooks**, then **+ Add Endpoint**. Enter your HTTPS URL and choose specific events or all events.
4. Copy the endpoint's signing secret and store it server-side, for example as `WETRAVEL_WEBHOOK_SECRET`.
5. 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.

```javascript
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:

```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](/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.*` and `transaction.*` for money movements rather than inferring them from `booking.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 `2xx` quickly 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](/webhooks/guides/implement-webhook-idempotency).

### Acknowledge fast and process asynchronously

Return a `2xx` as soon as the event is stored. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## 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](https://hookdeck.com) verifies WeTravel deliveries and holds each event until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle WeTravel webhooks reliably in minutes.