Skip to content

Webhooks ​

AvailEngine sends webhooks to your server when things happen. Your app stays in sync without polling.

Available Events ​

EventDescription
booking.createdA new booking was created
booking.confirmedDeposit paid, booking is now confirmed
booking.updatedBooking details changed (time, resource, etc.)
booking.checked_inCustomer has arrived
booking.completedService is done
booking.cancelledBooking was cancelled
booking.no_showCustomer did not show up
deposit.paidDeposit payment succeeded
deposit.capturedDeposit was charged (no-show or late cancellation)
deposit.releasedDeposit was refunded

Setting Up Webhooks ​

Register your endpoint:

bash
curl -X POST https://api.availengine.com/v1/manage/webhooks \
  -H "Authorization: Bearer avail_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/availengine",
    "events": ["booking.created", "booking.cancelled", "deposit.paid"]
  }'

AvailEngine generates a secret for this endpoint. Store it securely — you'll use it to verify payloads.

Payload Format ​

Every webhook is a POST with a JSON body:

json
{
  "event": "booking.created",
  "timestamp": "2026-05-15T14:00:00Z",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "customer": {
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@example.com"
    },
    "resource_id": "uuid",
    "booking_date": "2026-05-15",
    "start_time": "14:00",
    "end_time": "15:00",
    "status": "confirmed"
  }
}

Verifying Signatures ​

Every webhook includes an X-AvailEngine-Signature header. The header format is t={timestamp},v1={signature} — an HMAC-SHA256 of {timestamp}.{payload} keyed with your webhook secret. This prevents replay attacks by including a timestamp with a 5-minute tolerance window.

JavaScript ​

js
const crypto = require('crypto');

function verifyWebhook(payload, signatureHeader, secret, toleranceSeconds = 300) {
  // Parse the t={ts},v1={sig} header
  const parts = {};
  for (const part of signatureHeader.split(',')) {
    const [k, v] = part.split('=', 2);
    if (k && v) parts[k.trim()] = v.trim();
  }

  const ts = parts.t;
  const sig = parts.v1;
  if (!ts || !sig) return false;

  // Reject signatures older than tolerance
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(ts)) > toleranceSeconds) return false;

  // Recompute the signature
  const signed = `${ts}.${payload}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signed)
    .digest('hex');
  const expectedHeader = `t=${ts},v1=${expected}`;

  return crypto.timingSafeEqual(
    Buffer.from(expectedHeader),
    Buffer.from(signatureHeader)
  );
}

Python ​

python
import hmac
import hashlib
import time

def verify_webhook(payload: str, signature_header: str, secret: str, tolerance: int = 300) -> bool:
    # Parse the t={ts},v1={sig} header
    parts = {}
    for part in signature_header.split(","):
        try:
            k, v = part.split("=", 1)
            parts[k.strip()] = v.strip()
        except ValueError:
            continue

    ts = parts.get("t")
    sig = parts.get("v1")
    if not ts or not sig:
        return False

    # Reject signatures older than tolerance
    if abs(int(time.time()) - int(ts)) > tolerance:
        return False

    # Recompute the signature
    signed = f"{ts}.{payload}"
    expected_sig = hmac.new(
        secret.encode(), signed.encode(), hashlib.sha256
    ).hexdigest()
    expected_header = f"t={ts},v1={expected_sig}"

    return hmac.compare_digest(expected_header, signature_header)

Responding ​

Always respond with 200 OK within 10 seconds. If you return 4xx or 5xx, AvailEngine retries with exponential backoff (5s, 30s, 5m, 30m, 2h).

Do heavy work (email, SMS, calendar sync) in a background job — don't block the webhook handler.

Event Catalog ​

Every event has a standard envelope with event-specific data.

Envelope ​

json
{
  "event": "booking.created",
  "timestamp": "2026-05-15T14:00:00Z",
  "sandbox": false,
  "data": { ... }
}

booking.created ​

Fires when a new booking is created (public widget or staff dashboard).

json
{
  "event": "booking.created",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "customer": {
      "id": "uuid",
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@example.com",
      "phone": "+301234567890"
    },
    "resource": { "id": "uuid", "name": "Maria" },
    "booking_date": "2026-05-15",
    "start_time": "14:00",
    "end_time": "15:00",
    "capacity": 1,
    "status": "confirmed",
    "confirmation_code": "AV-ABC123",
    "source": "online",
    "customer_notes": "Prefers window seat",
    "deposit": null
  }
}

booking.confirmed ​

Fires when a pending booking transitions to confirmed (deposit paid or auto-confirmed).

json
{
  "event": "booking.confirmed",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "previous_status": "pending",
    "status": "confirmed",
    "confirmation_code": "AV-ABC123"
  }
}

booking.updated ​

Fires when booking details change (time, resource, capacity, notes).

json
{
  "event": "booking.updated",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "changes": {
      "resource_id": { "from": "old-uuid", "to": "new-uuid" },
      "start_time": { "from": "14:00", "to": "14:30" }
    }
  }
}

booking.checked_in ​

Fires when a customer arrives.

json
{
  "event": "booking.checked_in",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "confirmed_at": "2026-05-14T10:30:00Z",
    "checked_in_at": "2026-05-15T13:55:00Z",
    "confirmation_code": "AV-ABC123"
  }
}

booking.completed ​

Fires when the service or appointment is done.

json
{
  "event": "booking.completed",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "checked_in_at": "2026-05-15T14:05:00Z",
    "completed_at": "2026-05-15T14:35:00Z",
    "duration_minutes": 30
  }
}

booking.cancelled ​

Fires when a booking is cancelled by customer or staff.

json
{
  "event": "booking.cancelled",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "previous_status": "confirmed",
    "cancellation_reason": "Customer request",
    "cancelled_by": "staff",
    "deposit_action": "released",
    "deposit_amount_cents": 2000
  }
}

booking.no_show ​

Fires when a customer doesn't show up.

json
{
  "event": "booking.no_show",
  "data": {
    "booking_id": "uuid",
    "business_id": "uuid",
    "previous_status": "confirmed",
    "deposit_action": "captured",
    "deposit_amount_cents": 2000
  }
}

deposit.paid ​

Fires when a deposit payment succeeds.

json
{
  "event": "deposit.paid",
  "data": {
    "booking_id": "uuid",
    "deposit_id": "uuid",
    "business_id": "uuid",
    "amount_cents": 2000,
    "stripe_payment_intent_id": "pi_xxx",
    "booking_status_after": "confirmed"
  }
}

deposit.captured ​

Fires when a held deposit is captured (no-show or late cancellation).

json
{
  "event": "deposit.captured",
  "data": {
    "booking_id": "uuid",
    "deposit_id": "uuid",
    "business_id": "uuid",
    "amount_cents": 2000,
    "stripe_payment_intent_id": "pi_xxx",
    "reason": "no_show"
  }
}

deposit.released ​

Fires when a held deposit is refunded (timely cancellation).

json
{
  "event": "deposit.released",
  "data": {
    "booking_id": "uuid",
    "deposit_id": "uuid",
    "business_id": "uuid",
    "amount_cents": 2000,
    "stripe_payment_intent_id": "pi_xxx",
    "reason": "cancellation_within_policy"
  }
}

Testing Webhooks ​

Use the sandbox environment (avail_test_ keys) and the X-AvailEngine-Sandbox: true header. Sandbox webhooks go to your registered endpoints but are tagged with "sandbox": true.

Local Testing ​

Use a tool like webhook.site to receive webhooks during development. Register the webhook.site URL as your endpoint, then trigger test bookings. Inspect the payloads in real-time.

Retry Schedule ​

Failed deliveries retry with exponential backoff:

AttemptDelay
15 seconds
230 seconds
35 minutes
430 minutes
52 hours

After 5 failed attempts, the delivery is dropped. Monitor your webhook endpoint health to avoid losing events.

Released under the MIT License.