# YouCanBookMe Webhooks

YouCanBookMe (a scheduling tool) sends a webhook as a notification action: when a booking is
made, rescheduled or cancelled, and for reminders, follow-ups and no-shows. Webhooks are **not
signed**. You build the request yourself (URL, method, headers, body) and authenticate it by adding
a secret **Request Header** key/value on the webhook action, then checking that header on your
endpoint. Webhook availability varies by plan and the help articles disagree, so check your
current plan.

## When to Use This Skill

* How do I receive YouCanBookMe webhooks?
* How do I authenticate a YouCanBookMe webhook? (Does it sign requests? No: use a secret header)
* How do I handle a booking made, rescheduled or cancelled notification?
* Why does my YouCanBookMe webhook endpoint return 401?
* The webhook method and body are user-chosen. How do I accept any of them?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
const sha256 = (v) => crypto.createHash('sha256').update(String(v)).digest();

function verifyYoucanbookmeRequest(headers, {
  headerName = process.env.YOUCANBOOKME_WEBHOOK_HEADER || 'Authorization',
  secret = process.env.YOUCANBOOKME_WEBHOOK_SECRET,
} = {}) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[headerName.toLowerCase()]; // Node lowercases header names
  // Compare the WHOLE value (e.g. "Bearer abc" stays "Bearer abc"); don't parse a prefix.
  return Boolean(received) && crypto.timingSafeEqual(sha256(received), sha256(secret));
}

```

* The header **name is yours**: whatever key you typed under Request Headers on the webhook
  action. Set `YOUCANBOOKME_WEBHOOK_HEADER` to it (default `Authorization`). The lookup is
  case-insensitive.
* `YOUCANBOOKME_WEBHOOK_SECRET` is the exact, whole header value, including any `Bearer ` prefix
  you typed.
* Missing header or mismatch: return `401`. A body that isn't a JSON object: `400`. Handled: `200`.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Use HTTPS, and rotate the credential if it leaks.
* Read the body raw (`express.raw({ type: '*/*' })`) and parse JSON only after the credential check.
* The method is user-chosen, so accept `POST`, `PUT`, `PATCH`, `DELETE` and `GET`, and treat an
  empty body (typical for `GET`) as valid. Hookdeck's YouCanBookMe source pre-selects `POST`
  without managing the method, so allow whichever method your booking webhook uses on the source too.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/youcanbookme-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/youcanbookme-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/youcanbookme-webhooks/examples/fastapi/).

## Payload and Event Names

There is no fixed schema. The body is the template you write, using shorthand codes such as
`{START-ISO}` and `{EMAIL}`, and **nothing in the request names the trigger** unless your
template does. The examples assume you add an `event` field, for example:

```json
{ "event": "booking_made", "email": "{EMAIL}", "start": "{START-ISO}" }

```

| `event` (your choice, illustrative) | Notification action |
| --- | --- |
| `booking_made` | A booking is made |
| `booking_rescheduled` | A booking is rescheduled |
| `booking_cancelled` | A booking is cancelled |
| `reminder` | A reminder fires |
| `follow_up` | A follow-up fires |
| `no_show` | A no-show is recorded |

These strings are conventions this skill uses; YouCanBookMe does not send them. Use one webhook
action per trigger, each with its own `event` value in the template.

## Environment Variables

```bash
YOUCANBOOKME_WEBHOOK_HEADER=Authorization   # the Request Header key you chose
YOUCANBOOKME_WEBHOOK_SECRET=your-secret     # the exact header value you chose

```

## Local Development

YouCanBookMe can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 youcanbookme --path /webhooks/youcanbookme

```

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/youcanbookme-webhooks/references/overview.md) - What the webhook actions are, templates and event field
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/youcanbookme-webhooks/references/setup.md) - Add a webhook action with a secret request header
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/youcanbookme-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/youcanbookme-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests