# 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.