Webhooks
Webhooks allow Bundleport to send real-time notifications to your application when events occur, eliminating the need to poll for updates.
Overview
Instead of polling the API repeatedly to check for booking status changes, webhooks deliver events directly to your server as they happen:
- ✅ Real-time notifications
- ✅ Reduced API calls (saves rate limit quota)
- ✅ Better user experience (instant updates)
- ✅ Lower server load
Supported Events
| Event Type | Description | When It Fires |
|---|---|---|
booking.confirmed | Booking was successfully confirmed | After successful booking creation |
booking.cancelled | Booking was cancelled | When a booking is cancelled |
booking.modified | Booking details were modified | When booking information changes |
booking.failed | Booking attempt failed | When booking creation fails |
quote.price_changed | Price changed during quote | When rechecking reveals price change |
payment.required | Payment action required | When payment needs attention |
Setting Up Webhooks
1. Create a Webhook Endpoint
Create an HTTP endpoint in your application that can receive POST requests:
- Node.js/Express
- Python/Flask
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Your webhook secret (from Bundleport dashboard)
const WEBHOOK_SECRET = process.env.BUNDLEPORT_WEBHOOK_SECRET;
// Verify webhook signature
function verifySignature(payload, signature) {
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
const digest = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
app.post('/webhooks/bundleport', (req, res) => {
const signature = req.headers['x-bundleport-signature'];
const payload = JSON.stringify(req.body);
// Verify signature
if (!verifySignature(payload, signature)) {
return res.status(401).send('Invalid signature');
}
const { event, data, timestamp } = req.body;
// Process the event
switch (event) {
case 'booking.confirmed':
handleBookingConfirmed(data);
break;
case 'booking.cancelled':
handleBookingCancelled(data);
break;
// ... handle other events
}
res.status(200).send('OK');
});
function handleBookingConfirmed(data) {
console.log('Booking confirmed:', data.bookingId);
// Update your database, send notifications, etc.
}
function handleBookingCancelled(data) {
console.log('Booking cancelled:', data.bookingId);
// Update your database, process refunds, etc.
}
app.listen(3000);
from flask import Flask, request, jsonify
import hmac
import hashlib
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('BUNDLEPORT_WEBHOOK_SECRET')
def verify_signature(payload, signature):
hmac_obj = hmac.new(
WEBHOOK_SECRET.encode(),
payload.encode(),
hashlib.sha256
)
digest = hmac_obj.hexdigest()
return hmac.compare_digest(digest, signature)
@app.route('/webhooks/bundleport', methods=['POST'])
def webhook():
signature = request.headers.get('X-Bundleport-Signature')
payload = request.get_data(as_text=True)
# Verify signature
if not verify_signature(payload, signature):
return jsonify({'error': 'Invalid signature'}), 401
data = request.json
event = data.get('event')
# Process the event
if event == 'booking.confirmed':
handle_booking_confirmed(data['data'])
elif event == 'booking.cancelled':
handle_booking_cancelled(data['data'])
# ... handle other events
return jsonify({'status': 'OK'}), 200
def handle_booking_confirmed(data):
print(f"Booking confirmed: {data['bookingId']}")
# Update your database, send notifications, etc.
def handle_booking_cancelled(data):
print(f"Booking cancelled: {data['bookingId']}")
# Update your database, process refunds, etc.
if __name__ == '__main__':
app.run(port=3000)
2. Register Your Webhook URL
- Log in to app.bundleport.com
- Navigate to Settings → Webhooks
- Click Add Webhook
- Enter your webhook URL (must be HTTPS in production)
- Select the events you want to receive
- Save your webhook secret (you'll need it to verify signatures)
3. Test Your Webhook
Use the dashboard's "Test Webhook" feature to send a test event to your endpoint.
Webhook Payload Format
All webhook payloads follow this structure:
{
"event": "booking.confirmed",
"data": {
"bookingId": "BP-123456789",
"clientReference": "CUST-001",
"providerReference": "PROV-987654321",
"status": "CONFIRMED",
"hotel": {
"code": "12345",
"name": "Example Hotel"
},
"checkIn": "2025-12-15",
"checkOut": "2025-12-17",
"price": {
"currency": "EUR",
"net": 300.00,
"suggested": 360.00
}
},
"timestamp": "2025-12-08T10:30:00Z",
"id": "evt_abc123def456"
}
Security: Verifying Signatures
Always verify webhook signatures to ensure requests are from Bundleport:
- Bundleport signs each webhook with HMAC-SHA256
- Signature is sent in the
X-Bundleport-Signatureheader - Compute HMAC-SHA256 of the raw request body using your webhook secret
- Compare with the signature header
Never process webhooks without verifying signatures. This prevents unauthorized requests.
Retry Logic
Bundleport will retry failed webhook deliveries:
- Initial attempt: Immediate
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 15 minutes
- Retry 4: After 1 hour
- Retry 5: After 6 hours
If all retries fail, the event is logged in the dashboard for manual review.
Best Practices for Retries
- Return 200 OK quickly: Process events asynchronously
- Idempotency: Handle duplicate events gracefully
- Logging: Log all webhook events for debugging
app.post('/webhooks/bundleport', async (req, res) => {
// Verify signature
if (!verifySignature(req.body, req.headers['x-bundleport-signature'])) {
return res.status(401).send('Invalid signature');
}
// Return 200 immediately
res.status(200).send('OK');
// Process asynchronously
processWebhookEvent(req.body).catch(error => {
console.error('Webhook processing failed:', error);
// Queue for retry or alert
});
});
Event Reference
booking.confirmed
Fired when a booking is successfully confirmed.
{
"event": "booking.confirmed",
"data": {
"bookingId": "BP-123456789",
"clientReference": "CUST-001",
"providerReference": "PROV-987654321",
"status": "CONFIRMED",
"hotel": {
"code": "12345",
"name": "Example Hotel Barcelona"
},
"checkIn": "2025-12-15",
"checkOut": "2025-12-17",
"price": {
"currency": "EUR",
"net": 300.00,
"suggested": 360.00
},
"rooms": [
{
"description": "Standard Double Room",
"confirmationReference": "ROOM-001"
}
]
}
}
booking.cancelled
Fired when a booking is cancelled.
{
"event": "booking.cancelled",
"data": {
"bookingId": "BP-123456789",
"clientReference": "CUST-001",
"status": "CANCELLED",
"cancellationFee": {
"currency": "EUR",
"amount": 50.00
},
"cancelledAt": "2025-12-10T14:30:00Z"
}
}
booking.modified
Fired when booking details are modified.
{
"event": "booking.modified",
"data": {
"bookingId": "BP-123456789",
"changes": {
"checkIn": {
"old": "2025-12-15",
"new": "2025-12-16"
},
"price": {
"old": 300.00,
"new": 320.00
}
}
}
}
Testing Webhooks Locally
For local development, use a tunneling service:
- ngrok:
ngrok http 3000 - localtunnel:
lt --port 3000 - Cloudflare Tunnel:
cloudflared tunnel --url http://localhost:3000
Update your webhook URL in the dashboard to point to the tunnel URL.
Monitoring Webhooks
Monitor webhook delivery in the dashboard:
- Delivery status: Success/failure for each event
- Response times: How long your endpoint takes to respond
- Retry history: Failed deliveries and retry attempts
- Event logs: All events sent to your webhook
Next Steps
- Connect Hotels Overview - Understand the complete booking journey
- Error Handling Guide - Handle errors gracefully
- API Reference - Browse all endpoints