Webhooks

Subscribe to real-time events from Reservory. Webhooks enable you to sync bookings to your CRM, email platform, analytics, or custom systems.

Creating a Webhook Subscription

Create subscriptions at /dashboard/settings/webhooks in the operator dashboard.

POST /api/webhooks/endpoints
Authorization: Bearer <session_jwt>
Content-Type: application/json

{
  "url": "https://your-server.com/webhooks/reservory",
  "events": [
    "booking.created",
    "booking.confirmed",
    "booking.cancelled",
    "refund.created",
    "waiver.signed"
  ]
}

The response contains your signing_secret — store this securely. It will not be shown again.

Event Taxonomy

Reservory currently emits these events:

booking.created

A customer has created a booking (hold placed)

booking.confirmed

A booking has been confirmed and paid

booking.cancelled

A booking was cancelled (by customer or operator)

booking.checked_in

A customer checked in at the venue

booking.no_show

A booking was marked as no-show

refund.created

A refund was issued for a booking

refund.failed

A refund attempt failed

waiver.signed

A customer signed their waiver

customer.created

A new customer record was created

gift_card.created

A gift card was created

gift_card.redeemed

A gift card was used on a booking

capacity_alert.triggered

Slot capacity was exceeded (safety net)

Webhook Payload

Every webhook POST includes:

POST https://your-server.com/webhooks/reservory
X-Reservory-Timestamp: 1716170400
X-Reservory-Signature: v1,abc123def...
Content-Type: application/json

{
  "event_id": "evt_abc123",
  "event": "booking.confirmed",
  "created_at": "2026-05-20T12:00:00Z",
  "data": {
    "booking_id": "bk_456",
    "status": "confirmed",
    "slot_id": "slot_123",
    "customer_email": "alice@example.com",
    "customer_name": "Alice",
    "seat_count": 2,
    "total_cents": 3999,
    "created_at": "2026-05-20T11:55:00Z",
    "confirmed_at": "2026-05-20T12:00:00Z"
  }
}

Signature Verification

Verify webhook authenticity using HMAC-SHA256. The signature is computed over ${timestamp}.${rawBody}:

// Node.js example
import crypto from 'crypto';

function verifyWebhook(request, signingSecret) {
  const timestamp = request.headers['x-reservory-timestamp'];
  const signature = request.headers['x-reservory-signature'];
  const rawBody = request.rawBody; // buffer before JSON parse

  // Reject old timestamps (> 5 minutes)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    throw new Error('Webhook timestamp too old');
  }

  // Compute HMAC
  const message = `${timestamp}.${rawBody.toString()}`;
  const computed = crypto
    .createHmac('sha256', signingSecret)
    .update(message)
    .digest('hex');

  // Compare (constant-time)
  if (!crypto.timingSafeEqual(
    Buffer.from(computed),
    Buffer.from(signature.split(',')[1])
  )) {
    throw new Error('Webhook signature invalid');
  }
}

Retry Policy

If your endpoint returns a non-2xx status or times out (10s), Reservory retries with exponential backoff:

  • Attempt 1: immediate
  • Attempt 2: 5 minutes
  • Attempt 3: 30 minutes
  • Attempt 4: 2 hours
  • Attempt 5: 12 hours
  • Attempt 6: 24 hours (final)

After 6 failed attempts, the delivery is marked failed and logged to your /dashboard/settings/webhooks page.

Signing Secret Rotation

When you rotate a signing secret at /dashboard/settings/webhooks, the old secret remains valid for 7 days. This allows you to deploy your verification code before shutting off the old key.

Best Practices

  • Always verify the signature before processing.
  • Reject timestamps older than 5 minutes to prevent replay attacks.
  • Return 200 quickly; process heavy tasks asynchronously.
  • Store your signing secret in environment variables, never in code.
  • Log received webhook events for audit purposes.
  • Monitor your webhook endpoint logs at /dashboard/settings/webhooks to catch delivery failures.