# Dentally Webhooks

## When to Use This Skill

* How do I receive Dentally webhooks?
* How do I verify the `X-Dentally-Signature` header?
* Why is my Dentally webhook signature verification failing?
* How do I handle `appointment.created`, `patient.updated` or `payment.created` events?
* Why did Dentally disable my webhook endpoint?

## Verification (core)

Dentally signs the **raw POST body** with HMAC-SHA256, keyed on the secret string you set on the webhook endpoint (used as UTF-8). The signature is a **bare lowercase hex digest** in `X-Dentally-Signature`: no prefix, no timestamp. Validate the header is exactly 64 hex characters before decoding it (`Buffer.from(x, 'hex')` silently truncates invalid hex), then compare timing-safe.

Node:

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

function verify(rawBody, signatureHeader, secret) {
  if (!secret || typeof signatureHeader !== 'string') return false;
  if (!/^[0-9a-f]{64}$/i.test(signatureHeader)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(signatureHeader, 'hex'));
}

```

Python:

```python
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest().encode()
    # Compare bytes so a non-ASCII header is a mismatch (401), not a TypeError (500)
    return hmac.compare_digest(expected, signature_header.lower().encode())

```

In Express read the body with `express.raw({ type: '*/*' })` so verification works whatever `Content-Type` is sent.

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

## Common Event Types

| Event | Description |
| --- | --- |
| `appointment.created` | An appointment was booked |
| `appointment.updated` | An appointment changed (state, time, practitioner) |
| `appointment.deleted` | An appointment was deleted |
| `patient.created` | A patient record was created |
| `patient.updated` | A patient record changed |
| `patient.deleted` | A patient record was deleted |
| `payment.created` | A payment was recorded |
| `payment.updated` | A payment changed |
| `payment.deleted` | A payment was deleted |

> **For the full reference**, see [Dentally API: Webhooks](https://developer.dentally.co/#webhooks)

## Delivery Behaviour

* Payload: `{ "data": {...}, "event": "appointment.created", "object": "appointment", "user": {...} }`. `user` is the Dentally user who triggered the event, if any.
* Respond with any 2xx (`200` or `204`). Anything else triggers a retry (up to 10 times, increasing intervals).
* **More than 50 failed deliveries disables the webhook automatically.** Re-enable it in Developer Settings.
* Webhooks may arrive out of order; new event types may be added at any time. Ignore unknown events and return `204 No Content`.
* Webhook URLs must be HTTPS (plain HTTP is allowed only in the sandbox).

## Important Headers

| Header | Description |
| --- | --- |
| `X-Dentally-Signature` | Hex HMAC-SHA256 of the raw body |
| `User-Agent` | `Dentally-Webhook` |
| `Content-Type` | `application/json` |

## Environment Variables

```bash
DENTALLY_WEBHOOK_SECRET=your_webhook_secret   # The "secret" you set when creating the webhook endpoint

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 dentally --path /webhooks/dentally

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dentally-webhooks/references/overview.md) - Dentally webhook concepts and events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dentally-webhooks/references/setup.md) - Creating a webhook endpoint and secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dentally-webhooks/references/verification.md) - Signature verification details and gotchas