Skip to content
Last updated

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 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 specification
  4. Handle Webhook Events: Process incoming booking events

Workflow Diagram

DistributorBookableWebhook EndpointPhase 1: RegistrationStore Secret Key SecurelyPhase 2: ImplementationPhase 3: Webhook Deliveryalt[Signature Valid][Signature Invalid]loop[For each booking event]Error Handling & Retriesalt[Webhook Delivery Fails]1. Request OAuth2 Token2. Return Access Token3. POST /webhooks{callbackUrl}4. Return Secret Key5. Implement Webhook Endpointwith HMAC-SHA256 verification6. Generate HMAC-SHA256signature with secret key7. POST /{callbackUrl}Headers: X-API-Key: signatureBody: booking data8. Verify HMAC-SHA256signature using stored secret9. Process booking event10. Return 200 OK{success: true}11. Return 401 Unauthorized{success: false, error: "unauthorized"}12. Retry with exponential backoff(1, 2, 4, 8, 16, 32 minutes)13. Retry POST requestDistributorBookableWebhook Endpoint

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:

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.

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:

{
  "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:

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:

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)

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)

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

{
  "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:

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:

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

MethodEndpointDescription
POST/webhooksRegister a new webhook
PUT/webhooksUpdate existing webhook URL
DELETE/webhooksDelete webhook registration

All webhook management endpoints require OAuth2 authentication with partner:create scope.