# Webhook Integration Documentation
## Overview
The Bookable webhook system allows distributors to receive real-time notifications about key booking events — such as new bookings, updates, and cancellations. This documentation provides a complete guide to the integration process, including how to register webhooks and handle incoming notifications effectively.
## Integration Flow
1. **Register to Webhook Events**: Use the [Register to Webhook Events API](/apis/production/webhook/webhookapi) to register your callback URL
2. **Store Secret Key**: Save the returned secret key for data verification
3. **Implement the callback endpoint**: Create an endpoint that follows the [Handling Webhook Events API](/apis/production/webhook/bookablewebhookapi) specification
4. **Handle Webhook Events**: Process incoming booking events
### Workflow Diagram
```mermaid
sequenceDiagram
participant D as Distributor
participant BKB as Bookable
participant WE as Webhook Endpoint
Note over D,BKB: Phase 1: Registration
D->>BKB: 1. Request OAuth2 Token
BKB->>D: 2. Return Access Token
D->>BKB: 3. POST /webhooks
{callbackUrl}
BKB->>D: 4. Return Secret Key
Note over D: Store Secret Key Securely
Note over D,WE: Phase 2: Implementation
D->>WE: 5. Implement Webhook Endpoint
with HMAC-SHA256 verification
Note over BKB,WE: Phase 3: Webhook Delivery
loop For each booking event
BKB->>BKB: 6. Generate HMAC-SHA256
signature with secret key
BKB->>WE: 7. POST /{callbackUrl}
Headers: X-API-Key: signature
Body: booking data
WE->>WE: 8. Verify HMAC-SHA256
signature using stored secret
alt Signature Valid
WE->>WE: 9. Process booking event
WE->>BKB: 10. Return 200 OK
{success: true}
else Signature Invalid
WE->>BKB: 11. Return 401 Unauthorized
{success: false, error: "unauthorized"}
end
end
Note over BKB,WE: Error Handling & Retries
alt Webhook Delivery Fails
BKB->>BKB: 12. Retry with exponential backoff
(1, 2, 4, 8, 16, 32 minutes)
BKB->>WE: 13. Retry POST request
end
```
## Prerequisites
- OAuth2 client credentials
- HTTPS endpoint capable of receiving POST requests
- Ability to store and manage secret keys securely
## Step 1: Register Your Webhook
### Authentication
First, obtain an OAuth2 access token using the OAuth2 client credentials flow:
```bash
curl -X POST https://auth.bookabletech.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "api.bookabletech.com"
}'
```
### Register your Webhook Endpoint
> ℹ️ **Info**: In the following example, we assume `https://your-domain.com/webhooks/booking-notification` as the `callbackUrl`.
```bash
curl -X POST https://api.bookabletech.com/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://your-domain.com/webhooks/booking-notification"
}'
```
**Response:**
```json
{
"secretKey": "123e4567-e89b-12d3-a456-426655440000",
"callbackUrl": "https://your-domain.com/webhooks/booking-notification"
}
```
**⚠️ Important**: Store the `secretKey` securely. You'll need it to verify incoming webhook data.
### Update your the Webhook Endpoint
To update your callback URL:
```bash
curl -X PUT https://api.bookabletech.com/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://your-new-domain.com/webhooks/booking-notification"
}'
```
### Delete Webhook
To remove your webhook registration:
```bash
curl -X DELETE https://api.bookabletech.com/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
## Step 2: Implement the Webhook Endpoint
### Endpoint Specification
Your webhook endpoint must:
- Accept POST requests at the registered callback URL
- Handle JSON payloads
- Respond with appropriate HTTP status codes
- Implement proper error handling
### Supported Event Types
- `booking.updated` - Existing booking modified
### Example Implementation (Node.js/Express)
```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Your stored secret key from webhook registration
const WEBHOOK_SECRET = 'your-secret-key-here';
app.post('/webhooks/booking-notification', (req, res) => {
try {
// Verify webhook authenticity (implement your verification logic)
if (!verifyWebhookSignature(req)) {
return res.status(401).json({
success: false,
error: 'unauthorized',
message: 'Invalid webhook signature'
});
}
const { eventType, timestamp, booking } = req.body;
// Validate required fields
if (!eventType || !timestamp) {
return res.status(400).json({
success: false,
error: 'validation_error',
message: 'Missing required fields: eventType, timestamp'
});
}
// Process the booking notification
switch (eventType) {
case 'booking.updated':
handleBookingUpdated(booking);
break;
default:
console.warn(`Unknown event type: ${eventType}`);
}
// Return success response
res.status(204)..end();
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({
success: false,
error: 'internal_error',
message: 'Failed to process webhook notification'
});
}
});
function verifyWebhookSignature(req) {
// Verify webhook signature using HMAC-SHA256
// The signature is sent in the X-API-Key header
const signature = req.headers['x-api-key'];
if (!signature) {
return false;
}
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
function handleBookingUpdated(booking) {
console.log('Booking updated:', booking[0]?.id);
// Implement your booking update logic
}
```
### Example Implementation (Python/Flask)
```python
from flask import Flask, request, jsonify
import hashlib
import hmac
import json
app = Flask(__name__)
# Your stored secret key from webhook registration
WEBHOOK_SECRET = 'your-secret-key-here'
@app.route('/webhooks/booking-notification', methods=['POST'])
def handle_booking_notification():
try:
# Verify webhook authenticity
if not verify_webhook_signature(request):
return jsonify({
'success': False,
'error': 'unauthorized',
'message': 'Invalid webhook signature'
}), 401
data = request.get_json()
event_type = data.get('eventType')
timestamp = data.get('timestamp')
booking = data.get('booking')
# Validate required fields
if not event_type or not timestamp:
return jsonify({
'success': False,
'error': 'validation_error',
'message': 'Missing required fields: eventType, timestamp'
}), 400
# Process the booking notification
if event_type == 'booking.updated':
handle_booking_updated(booking)
else:
print(f"Unknown event type: {event_type}")
return '', 204
except Exception as e:
print(f"Webhook processing error: {e}")
return jsonify({
'success': False,
'error': 'internal_error',
'message': 'Failed to process webhook notification'
}), 500
def verify_webhook_signature(request, secret_key):
"""
Verify webhook signature using HMAC-SHA256.
The signature is sent in the X-API-Key header.
"""
signature = request.headers.get('X-API-Key')
if not signature:
return False
payload = json.dumps(request.get_json(), separators=(',', ':'))
expected_signature = hmac.new(
secret_key.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
def handle_booking_updated(booking):
print(f"Booking updated: {booking[0]['id'] if booking else 'Unknown'}")
# Implement your booking update logic
```
## Webhook Payload Structure
### Booking Notification Format
```json
{
"eventType": "booking.created|booking.updated|booking.cancelled",
"timestamp": "2025-06-17T10:30:00Z",
"booking": [
{
"id": "12345678-1234-1234-1234-123456789012",
"venueId": "87654321-4321-4321-4321-210987654321",
"date": "2025-06-25",
"time": "19:30:00",
"partySize": 4,
"status": "confirmed",
"reference": "REF-20250617-001",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": "+1234567890",
"company": "Acme Corp",
"location": {
"address": "123 Main Street",
"city": "New York",
"state": "NY",
"country": "US",
"postalCode": "10001"
},
"duration": 120,
"productType": "dinner",
"notes": "Window table preferred",
"createdDate": "2025-06-17T09:15:00Z",
"lastUpdate": "2025-06-17T10:30:00Z"
}
]
}
```
### Booking Status Values
- `pending` - Booking is awaiting confirmation
- `confirmed` - Booking has been confirmed
- `cancelled` - Booking has been cancelled
- `completed` - Booking has been completed
- `no_show` - Customer did not show up
## Security Considerations
### HMAC-SHA256 Signature Verification
The webhook system uses HMAC-SHA256 for payload verification. Here's how it works:
1. **Signature Generation**: The Bookings Group system generates a signature using:
- **Payload**: The complete JSON webhook payload
- **Secret Key**: Your webhook secret token
- **Algorithm**: HMAC-SHA256
2. **Signature Delivery**: The generated signature is sent in the `X-API-Key` header with each webhook request
3. **Verification Process**: Your endpoint should:
- Extract the signature from the `X-API-Key` header
- Generate the expected signature using the same payload and secret
- Compare signatures using a timing-safe comparison function
### Data Verification
The webhook secret key is used to generate an HMAC-SHA256 signature for payload verification. The Bookings Group system generates a signature using the JSON payload and your secret token, which is sent in the `X-API-Key` header.
Use the secret key provided during webhook registration to verify that incoming requests are legitimate:
```javascript
function verifyWebhookSignature(req, secretKey) {
const signature = req.headers['x-api-key']; // Signature sent in X-API-Key header
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
```
### Best Practices
1. **HTTPS Only**: Only use HTTPS endpoints for webhook URLs
2. **Validate Payloads**: Always validate incoming data structure
3. **Idempotency**: Handle duplicate notifications gracefully
4. **Error Handling**: Implement proper error responses
5. **Logging**: Log webhook events for debugging and monitoring
6. **Rate Limiting**: Implement rate limiting on your webhook endpoint
## Error Handling
### Response Codes
Your webhook endpoint should return appropriate HTTP status codes:
- `204` - Success: Notification processed successfully
- `400` - Bad Request: Invalid payload or missing required fields
- `401` - Unauthorized: Invalid authentication or signature
- `500` - Internal Server Error: Processing failed
### Retry Logic
The Bookings Group system will retry failed webhook deliveries with exponential backoff:
- Initial retry after 1 minute
- Subsequent retries at 2, 4, 8, 16, 32 minutes
- Maximum of 5 retry attempts
- Webhooks failing all retries will be logged for manual review
## Testing
### Test Webhook Endpoint
Create a simple test endpoint to verify your implementation:
```bash
curl -X POST https://your-domain.com/webhooks/booking-notification \
-H "Content-Type: application/json" \
-d '{
"eventType": "booking.created",
"timestamp": "2025-06-17T10:30:00Z",
"booking": [{
"id": "test-booking-id",
"venueId": "test-venue-id",
"date": "2025-06-25",
"time": "19:30:00",
"partySize": 4,
"status": "confirmed",
"reference": "TEST-REF-001",
"firstName": "Test",
"lastName": "User",
"email": "test@example.com",
"phone": "+1234567890",
"duration": 120,
"notes": "Test booking",
"createdDate": "2025-06-17T10:30:00Z",
"lastUpdate": "2025-06-17T10:30:00Z"
}]
}'
```
## Troubleshooting
### Common Issues
1. **Not Receiving Webhook Events**
- Verify your callback URL is accessible from the internet
- Check that your endpoint returns correct HTTP status codes
- Ensure your server can handle HTTPS requests
2. **Authentication Failures**
- Verify your OAuth2 credentials are correct
- Ensure you're including the `audience` parameter in token requests
3. **Payload Validation Errors**
- Verify your endpoint accepts JSON content
- Check that required fields are being processed
- Validate the structure matches the specification
### Support
For technical support or questions about webhook integration:
- **Email**: hello@bookabletech.com
## API Endpoints Reference
### Staging Environment
- **Base URL**: `https://api-staging.bookabletech.com`
- **Auth URL**: `https://bookabletech.uk.auth0.com/oauth/token`
### Production Environment
- **Base URL**: `https://api.bookabletech.com`
- **Auth URL**: `https://auth.bookabletech.com/oauth/token`
### Webhook Management Endpoints
| Method | Endpoint | Description |
| --- | --- | --- |
| POST | `/webhooks` | Register a new webhook |
| PUT | `/webhooks` | Update existing webhook URL |
| DELETE | `/webhooks` | Delete webhook registration |
All webhook management endpoints require OAuth2 authentication with `partner:create` scope.