Events

Webhooks

Calendeur calls your server the moment something changes, so your systems stay in sync without polling.

Set up an endpoint

  1. 1

    Expose a public HTTPS URL

    It must accept POST requests with a JSON body and answer with a 2xx status within 10 seconds. Do slow work after responding. Private and internal addresses (localhost, 10.x, 192.168.x, cloud metadata) are rejected.

  2. 2

    Subscribe it

    In the dashboard under Integrations → Webhooks, or with the API (scope manage:webhooks):

    Request
    curl -X POST https://calendeur.com/api/v1/webhooks \
      -H "Authorization: Bearer $CALENDEUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url":"https://api.example.com/calendeur","events":["booking.created","booking.cancelled"]}'

    Store the secret from the response; it's shown only once.

  3. 3

    Verify every delivery

    Check the signature before trusting a payload. Anyone can send a request to your URL; only Calendeur knows the secret.

Payload

Each delivery is a POST with these headers and body:

Headers
Content-Type: application/json
User-Agent: Calendeur-Webhooks/1.0
X-Calendeur-Event: booking.created
X-Calendeur-Signature: sha256=5d1f0c...
Body
{
  "event": "booking.created",
  "timestamp": "2026-10-05T18:22:05.000Z",
  "data": {
    "bookingId": "bkg_8f2k1q",
    "eventTypeId": "evt_30min",
    "guestName": "Alex Morgan",
    "guestEmail": "alex@example.com",
    "guestTimezone": "America/Chicago",
    "startTime": "2026-10-12T15:00:00.000Z",
    "endTime": "2026-10-12T15:30:00.000Z",
    "duration": 30,
    "status": "UPCOMING"
  }
}

Verifying signatures

X-Calendeur-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with your endpoint's secret. Compute it over the exact bytes received (before JSON parsing) and compare in constant time.

import crypto from "crypto"
import express from "express"

const app = express()

// Use the raw body: the signature covers the exact bytes we sent.
app.post("/calendeur", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.CALENDEUR_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex")
  const given = req.get("X-Calendeur-Signature") || ""

  const valid = given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))
  if (!valid) return res.sendStatus(401)

  const { event, data } = JSON.parse(req.body.toString())
  if (event === "booking.created") {
    // e.g. create the appointment in your own database
  }
  res.sendStatus(200)
})

Handle duplicates

Make your handler idempotent: use data.bookingId plus event as a key so processing the same delivery twice has no extra effect.

Events

booking.createdA meeting was booked, from any channel.
booking.rescheduledA meeting moved to a new time.
booking.cancelledA meeting was cancelled by the host or guest.
booking.completedA meeting was marked completed.
booking.no_showThe guest was marked as a no-show.
booking.paidPayment for a paid booking was received.
meeting.notes_addedNotes or a transcript were added and summarized.
routing.submittedSomeone submitted a routing form.
document.sentA document was sent for signature.
document.signedA signer signed a document.
document.completedEveryone has signed a document.
document.declinedA signer declined a document.
voice.call.startedAn AI voice call started.
voice.call.completedAn AI voice call ended.
voice.call.meeting_bookedAn AI voice call booked a meeting.
voice.campaign.completedAn AI voice campaign finished.

Manage subscriptions with the Webhooks API.