Skip to main content

Overview

Webhooks allow you to receive real-time HTTP notifications when booking events occur. Instead of polling the API for changes, Kordless pushes data to your server as events happen.
Webhooks are configured in the Kordless platform under CalendarSettingsWebhooks.

Available Events

EventDescription
booking.createdA new booking was created
booking.updatedA booking was rescheduled
booking.cancelledA booking was cancelled

Setting Up Webhooks

1

Create an Endpoint

Create an HTTPS endpoint on your server to receive webhook events:
// Example: Express.js
app.post('/webhooks/kordless', (req, res) => {
  const event = req.body;
  console.log('Received:', event.event);
  res.status(200).send('OK');
});
2

Register in Platform

  1. Go to CalendarSettingsWebhooks
  2. Click Add Webhook
  3. Enter your endpoint URL (must be HTTPS)
  4. Select the events you want to receive
  5. Copy the webhook secret for signature verification
3

Verify Signatures

Implement signature verification to ensure requests are from Kordless.

Webhook Payload Format

When an event occurs, Kordless sends an HTTP POST request to your registered URL:
{
  "event": "booking.created",
  "timestamp": "2025-12-03T14:00:00Z",
  "webhookId": "whk_abc123xyz",
  "data": {
    "booking": {
      "id": "book_abc123",
      "confirmation_number": "BOOK_ABC123",
      "serviceId": "srv_haircut",
      "startsAt": "2025-12-03T14:00:00Z",
      "endsAt": "2025-12-03T15:00:00Z",
      "status": "confirmed",
      "contact": {
        "email": "[email protected]",
        "name": "Jane Doe",
        "phone": "+1234567890"
      }
    }
  }
}

Webhook Headers

Each webhook request includes these headers:
HeaderDescription
Content-TypeAlways application/json
X-Kordless-SignatureHMAC-SHA256 signature for verification
X-Kordless-TimestampUnix timestamp when webhook was sent
X-Kordless-Webhook-IdUnique ID for this webhook delivery

Verifying Webhook Signatures

Always verify webhook signatures to ensure requests are from Kordless. Use the webhook secret from your platform settings.
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
app.post('/webhooks/kordless', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-kordless-signature'];
  const timestamp = req.headers['x-kordless-timestamp'];
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(payload);

  switch (event.event) {
    case 'booking.created':
      handleBookingCreated(event.data.booking);
      break;
    case 'booking.updated':
      handleBookingUpdated(event.data.booking);
      break;
    case 'booking.cancelled':
      handleBookingCancelled(event.data.booking);
      break;
  }

  res.status(200).send('OK');
});

Example Event Handlers

function handleBookingCreated(booking) {
  console.log(`New booking: ${booking.confirmation_number}`);

  // Send confirmation email
  sendEmail({
    to: booking.contact.email,
    subject: 'Booking Confirmed',
    body: `Your appointment is confirmed for ${formatDate(booking.startsAt)}`
  });

  // Update your CRM
  updateCRM(booking.contact);

  // Sync to your calendar
  createCalendarEvent(booking);
}

function handleBookingUpdated(booking) {
  console.log(`Booking rescheduled: ${booking.confirmation_number}`);

  // Notify customer of new time
  sendEmail({
    to: booking.contact.email,
    subject: 'Booking Rescheduled',
    body: `Your appointment has been moved to ${formatDate(booking.startsAt)}`
  });
}

function handleBookingCancelled(booking) {
  console.log(`Booking cancelled: ${booking.confirmation_number}`);

  // Send cancellation confirmation
  sendEmail({
    to: booking.contact.email,
    subject: 'Booking Cancelled',
    body: 'Your appointment has been cancelled.'
  });

  // Update your records
  markAsCancelled(booking.id);
}

Best Practices

Return a 2xx response within 30 seconds. Process webhook data asynchronously:
app.post('/webhooks/kordless', async (req, res) => {
  // Verify signature first
  if (!verifySignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  // Respond immediately
  res.status(200).send('OK');

  // Process async (don't await)
  processWebhookAsync(req.body);
});

async function processWebhookAsync(event) {
  // Queue for background processing
  await queue.add('webhook', event);
}
Kordless retries failed webhooks. Make your handler idempotent:
async function handleBookingCreated(booking, webhookDeliveryId) {
  // Check if already processed
  const existing = await db.webhookEvents.findOne({
    webhookDeliveryId
  });

  if (existing) {
    console.log('Already processed, skipping');
    return;
  }

  // Process and mark as handled
  await processBooking(booking);
  await db.webhookEvents.create({
    webhookDeliveryId,
    processedAt: new Date()
  });
}
Webhook URLs must use HTTPS:
✅ https://yourapp.com/webhooks/kordless
❌ http://yourapp.com/webhooks/kordless
Keep your webhook secret in environment variables:
// ✅ Good
const secret = process.env.KORDLESS_WEBHOOK_SECRET;

// ❌ Bad - Never hardcode
const secret = 'whsec_abc123...';
Log all webhook events for debugging:
app.post('/webhooks/kordless', (req, res) => {
  console.log('Webhook received:', {
    event: req.body.event,
    timestamp: req.body.timestamp,
    webhookId: req.headers['x-kordless-webhook-id']
  });

  // Process...
});

Retry Policy

If your endpoint doesn’t respond with a 2xx status code, Kordless retries:
AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
After 5 failed attempts, the webhook is marked as failed.

Use Cases

Sync to External Calendar

async function handleBookingCreated(booking) {
  await googleCalendar.events.insert({
    calendarId: 'primary',
    resource: {
      summary: `${booking.service_name} - ${booking.contact.name}`,
      start: { dateTime: booking.startsAt },
      end: { dateTime: booking.endsAt },
      attendees: [{ email: booking.contact.email }]
    }
  });
}

Update CRM

async function handleBookingCreated(booking) {
  await crm.contacts.upsert({
    email: booking.contact.email,
    name: booking.contact.name,
    phone: booking.contact.phone,
    lastBooking: booking.startsAt
  });
}

Send SMS Notifications

async function handleBookingCreated(booking) {
  if (booking.contact.phone) {
    await twilio.messages.create({
      to: booking.contact.phone,
      from: TWILIO_NUMBER,
      body: `Confirmed: ${booking.service_name} on ${formatDate(booking.startsAt)}. Confirmation: ${booking.confirmation_number}`
    });
  }
}