Every booking, sent where your team works

Microsoft Bookings has no webhooks. Switch on Store bookings for a widget and BookingsXP sends each booking to Zapier, Make, n8n, Power Automate, your CRM, a Teams channel or Slack, with the campaign that brought it.

Off by default. Nothing personal is stored until you turn it on.

BookingsXP bookings list with appointment time, reference, customer, service, staff member and traffic source for each booking
The Bookings list, with Store bookings on: each booking with its reference and the source that brought it. Sample data for a fictional clinic.

Off by default, on when you need it

Out of the box, BookingsXP keeps no names, emails or answers. The booking lives in your Microsoft 365 tenant and the dashboard counts anonymous visits. “Store bookings in BookingsXP” is a per-widget switch, for the widgets where you want the data in other tools.
What BookingsXP stores with Store bookings off and on
DataStore bookings off (default)Store bookings on
Funnel eventsAnonymous: the step, time, page, source, medium, campaign, device, country and a random per-visit IDThe same anonymous events
Name, email and phoneNot stored. They go to Microsoft Bookings and nowhere elseStored with the booking
Notes and answers to your questionsNot storedStored with the booking
Campaign detailSource, medium, campaign and channel, with no link to a personAlso the full UTMs, click IDs, first page and referrer, attached to the booking
IP addressesNever storedNever stored
Bookings list, search and CSVNot availableAvailable (CSV export on Pro and Business)
Webhooks, Teams and Slack alertsNot availableAvailable on Pro and Business
How long it is keptEvents: 30 days on Free, 12 months on Pro, 24 months on BusinessBookings: 30 days on Free, 12 months on Pro, 24 months on Business

Search, export and erase

With Store bookings on, every booking made through the widget appears in one list across all your widgets and locations, with the source that brought it.
Search
Find a booking by name, email, reference, service or campaign. Useful when a customer quotes a BXP reference, or when sales asks which bookings came from one campaign.
CSV export
Every stored booking with its customer, answers and attribution, for spreadsheets and CRMs that import files (Pro and Business).
Erase by email
Delete every stored booking for one email address in one step, for data-subject requests. Microsoft Bookings is not changed.
Delete one, or stop collecting
Delete a single booking at any time. Switching Store bookings off stops new copies; existing ones are deleted when they pass your plan's retention period.

Signed JSON for anything that accepts a URL

Each booking is POSTed as JSON to the endpoints you add, signed with HMAC-SHA256 so you can prove it came from BookingsXP. The full reference is in the webhooks docs.

booking.created payload

JSON
{
  "id": "evt_4f1c9b2e7a6d4e0f8b3a5c7d9e1f2a4b",
  "type": "booking.created",
  "created": "2026-09-29T15:02:14.318Z",
  "apiVersion": "2026-09-01",
  "data": {
    "booking": {
      "reference": "BXP-7K3M9Q",
      "id": "AAMkADk2ZTJhNzQwLWE3YjEtNGQ1Mi05ZjE0LTgyNmQ0ZDhjOWUxMwBGAAA=",
      "status": "confirmed",
      "start": "2026-10-14T14:15:00.000Z",
      "end": "2026-10-14T15:00:00.000Z",
      "timeZone": "America/New_York",
      "service": {
        "id": "8b3c2f4e-1d7a-4c59-9e0b-6a2f71d4c8e3",
        "name": "Initial assessment"
      },
      "staff": [
        {
          "id": "f41e9a27-3c80-4b6d-a5e2-0d9c7b18e664",
          "name": "Dr. Maya Chen"
        }
      ],
      "customer": {
        "name": "Alex Rivera",
        "email": "alex.rivera@example.com",
        "phone": "+1 555 0142",
        "notes": "Left knee, running injury."
      },
      "answers": [
        {
          "questionId": "2c7d51a9-86e4-4f03-b1da-5e9f0c3a7b42",
          "question": "How did you hear about us?",
          "answer": "google / cpc / spring-physio"
        }
      ],
      "manageUrl": "https://outlook.office.com/book/BrightwaterClinic@brightwater.example/id/3f9a6c1e-72d4-4b8e-a015-c6e2d9b7f840?isAnonymous=true",
      "joinUrl": null
    },
    "business": {
      "id": "BrightwaterClinic@brightwater.example",
      "name": "Brightwater Clinic"
    },
    "widget": {
      "id": "w_8fk2m1qz",
      "name": "Physio page"
    },
    "attribution": {
      "source": "google",
      "medium": "cpc",
      "campaign": "spring-physio",
      "channel": "Paid search",
      "landingPage": "/physio?area=knee&utm_source=google&utm_medium=cpc&utm_campaign=spring-physio&utm_term=sports+physio+near+me&gclid=Cj0KCQjw8p2Z",
      "referrer": "https://www.google.com/",
      "pageUrl": "https://brightwater.example/book",
      "utm": {
        "source": "google",
        "medium": "cpc",
        "campaign": "spring-physio",
        "term": "sports physio near me"
      },
      "clickIds": {
        "gclid": "Cj0KCQjw8p2Z"
      },
      "gaClientId": "1843021957.1760190322"
    }
  }
}

Request headers

HTTP
POST /your-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: BookingsXP-Webhooks/1.0 (+https://bookingsxp.com/docs/webhooks)
BookingsXP-Event: booking.created
BookingsXP-Delivery: 5b0e1c7a-…
BookingsXP-Signature: t=1790694134,v1=5f2c8e…

Verify the signature (Node)

The signature is the hex HMAC-SHA256 of <t>.<raw body> with your endpoint's whsec_… secret. Reject timestamps more than five minutes old.

JavaScript
import crypto from "node:crypto";
import express from "express";

// The endpoint's signing secret (whsec_…), shown once when you add it.
const SECRET = process.env.BOOKINGSXP_WEBHOOK_SECRET;

function verify(rawBody, header, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

// Sign-checking needs the raw body, exactly as it was sent.
app.post("/bookingsxp", express.raw({ type: "application/json" }), (req, res) => {
  const body = req.body.toString("utf8");
  if (!verify(body, req.get("BookingsXP-Signature") ?? "")) return res.sendStatus(400);

  const event = JSON.parse(body);
  if (event.type === "booking.created") {
    const { booking, attribution } = event.data;
    // booking.customer.email, booking.service.name, attribution.campaign …
  }
  res.sendStatus(200);
});

An alert in the channel, not another inbox

Add a Microsoft Teams or Slack endpoint and each booking arrives as a formatted card: who booked, what, when, with whom, and the source and campaign. Teams uses the Workflows app's webhook; Slack uses an incoming webhook.
Microsoft Teams channel, as an Adaptive CardIllustrative

Workflows11:02 AM

New booking: Alex Rivera · Initial assessment · Wed, Oct 14, 10:15 AM EDT

Service
Initial assessment
When
Wed, Oct 14, 10:15 AM EDT
With
Dr. Maya Chen
Source
google / cpc · spring-physio
Customer
Alex Rivera (alex.rivera@example.com)
Reference
BXP-7K3M9Q

via Physio page · Brightwater Clinic

Slack channel, from an incoming webhookIllustrative

BookingsXP11:02 AM

New booking: Alex Rivera · Initial assessment · Wed, Oct 14, 10:15 AM EDT

Service
Initial assessment
When
Wed, Oct 14, 10:15 AM EDT
With
Dr. Maya Chen
Source
google / cpc · spring-physio
Customer
Alex Rivera alex.rivera@example.com
Ref
BXP-7K3M9Q

via Physio page · Brightwater Clinic

The text is what BookingsXP sends for a sample booking; Teams and Slack draw it in their own style. BookingsXP is independent and not affiliated with or endorsed by Microsoft or Slack.

Retries and a delivery log

If an endpoint fails, BookingsXP tries again up to 8 times with exponential backoff, from 30 seconds up to about an hour apart, over roughly two hours. Every attempt is logged with its status code and duration, and you can redeliver by hand.
BookingsXP integrations page with a form to add a webhook, Microsoft Teams or Slack endpoint, the list of endpoints and recent successful deliveries
The Integrations page: endpoints, test buttons and recent deliveries. Sample data for a fictional clinic.

A webhook problem never fails a booking. Microsoft Bookings has already confirmed it by the time the webhook is queued. Delivery logs are kept for 30 days.

Pull bookings and analytics on your schedule

On Business, create API keys in the dashboard and read your widgets, stored bookings and analytics from your own systems. Details in the REST API docs.
GET /api/v1/widgets
Your widgets: ID, name, Bookings link, template, status, whether bookings are stored.
GET /api/v1/bookings
Stored bookings, newest first, with customer, answers and attribution. Filter by since, until and widget; page with cursor.
GET /api/v1/analytics
Totals, daily series, funnel, channels, sources and campaigns for up to 730 days.
Shell
curl "https://bookingsxp.com/api/v1/bookings?since=2026-09-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer bxp_live_…"
Response (abridged)
{
  "data": [
    {
      "reference": "BXP-7K3M9Q",
      "status": "confirmed",
      "createdAt": "2026-09-29T15:02:14.318Z",
      "start": "2026-10-14T14:15:00.000Z",
      "service": { "id": "8b3c2f4e-…", "name": "Initial assessment" },
      "customer": { "name": "Alex Rivera", "email": "alex.rivera@example.com", … },
      "attribution": { "source": "google", "medium": "cpc", "campaign": "spring-physio", … }
    }
  ],
  "nextCursor": null
}

Questions about webhooks and data

Bookings, where you work

Start free, keep Microsoft Bookings as it is, and switch on Store bookings for the widgets you want to automate.

No Microsoft sign-in or admin consent. Free plan, no card.