Skip to content
Last updated

Webhook Guide

The Bookable webhook system delivers real-time notifications to your endpoint when booking events occur — such as new bookings, updates, and cancellations.

Integration flow

  1. Register your callback URL via the API and receive a secretKey
  2. Store the secret key securely — you'll use it to verify every incoming request
  3. Implement an HTTPS endpoint that accepts POST requests and verifies the HMAC-SHA256 signature
  4. Respond with 204 No Content on success
Your Webhook EndpointBookableYour AppYour Webhook EndpointBookableYour AppPhase 1: RegistrationStore secretKey securelyPhase 2: Event Deliveryalt[Valid][Invalid]loop[For each booking event]Failed deliveries retry with exponential backoffPOST /webhooks {callbackUrl}{ secretKey }Generate HMAC-SHA256 signaturePOST {callbackUrl}X-API-Key: signatureVerify signature204 No Content401 Unauthorized

Step 1: Register your webhook

Obtain an access token

See Authentication for the full token flow. Quick reference:

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 callback URL

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"
}

⚠️ Store the secret key securely

The secretKey is returned only once at registration. Store it in a secrets manager or environment variable — never hardcode it.

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 your webhook

curl -X DELETE https://api.bookabletech.com/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Step 2: Implement your webhook endpoint

Your endpoint must accept POST requests, verify the HMAC-SHA256 signature, process the event, and respond with 204 No Content.

Supported event types

EventDescription
booking.createdA new booking was created
booking.updatedAn existing booking was modified
booking.cancelledA booking was cancelled

Full endpoint implementation

// Node.js / Express
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.BOOKABLE_WEBHOOK_SECRET;

app.post('/webhooks/booking-notification', (req, res) => {
  try {
    if (!verifySignature(req.headers['x-api-key'], req.body, WEBHOOK_SECRET)) {
      return res.status(401).json({ success: false, error: 'unauthorized' });
    }

    const { eventType, timestamp, booking } = req.body;

    if (!eventType || !timestamp) {
      return res.status(400).json({ success: false, error: 'validation_error' });
    }

    switch (eventType) {
      case 'booking.created':
      case 'booking.updated':
      case 'booking.cancelled':
        console.log(`Event: ${eventType}, booking ID: ${booking[0]?.id}`);
        // Handle event...
        break;
      default:
        console.warn(`Unknown event type: ${eventType}`);
    }

    res.status(204).end();

  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ success: false, error: 'internal_error' });
  }
});

function verifySignature(signature, body, secret) {
  if (!signature) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(body))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

Webhook payload

{
  "eventType": "booking.created",
  "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",
      "duration": 120,
      "notes": "Window table preferred",
      "createdDate": "2025-06-17T09:15:00Z",
      "lastUpdate": "2025-06-17T10:30:00Z"
    }
  ]
}

Booking status values

StatusDescription
pendingAwaiting operator confirmation
confirmedBooking confirmed
cancelledBooking cancelled
completedBooking completed
no_showGuest did not show up

Signature verification

Every webhook request includes an X-API-Key header containing an HMAC-SHA256 hex digest of the raw request body, signed with your secretKey.

Verification steps:

  1. Read the raw request body as a string (before any JSON parsing)
  2. Compute HMAC-SHA256(secretKey, rawBody) and hex-encode it
  3. Compare the result to the X-API-Key header using a timing-safe comparison

⚠️ Use the raw body

Always compute the signature on the raw request body string, not a re-serialised version of the parsed JSON — key ordering and whitespace must be identical.


Error handling & retries

Your endpoint should return appropriate HTTP status codes:

CodeMeaning
204Success — event processed
400Bad request — invalid payload
401Unauthorized — signature invalid
500Server error — processing failed

Failed deliveries are retried with exponential backoff: 1, 2, 4, 8, 16, and 32 minutes. After 6 attempts the event is dropped and logged for review.


Best practices

  1. HTTPS only — never register an HTTP callback URL
  2. Verify every request — always validate the signature before processing
  3. Respond quickly — return 204 immediately and process the event asynchronously if needed
  4. Handle duplicates — retries can deliver the same event more than once; make your handler idempotent
  5. Log everything — store raw payloads for debugging and auditing

Troubleshooting

SymptomCheck
Not receiving eventsIs your callback URL publicly reachable over HTTPS? Does it return 204?
Signature mismatchAre you hashing the raw body before JSON parsing? Is the stored secret correct?
Auth failuresAre your OAuth2 credentials valid? Is audience set to api.bookabletech.com?

Support: hello@bookabletech.com