> ## Documentation Index
> Fetch the complete documentation index at: https://build.practicehub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Be told when something changes — from the API, the app, or the background

<Warning>
  **Coming soon.** Webhook delivery is not switched on yet. This page describes the contract that will ship — event types, payload shape, signing and delivery behaviour — so you can build your receiver ahead of time. Until then, poll with `updated` filters and `deleted_entities` (see [Querying](/guides/querying)). The changelog will announce availability.
</Warning>

Instead of polling, subscribe an HTTPS endpoint and PracticeHub will call it when records change on the account. Events fire for changes made **through the API, in the PracticeHub app by staff, and by background jobs** alike.

<Note>
  Webhooks are available on accounts created on PracticeHub 2.0. Existing accounts get them when they complete their move to 2.0 — until then, poll `deleted_entities` and `updated` filters as before.
</Note>

## Setting up an endpoint

<Steps>
  <Step title="Open the portal">
    In PracticeHub go to **Developers → Webhooks → Manage endpoints**. This opens the account's webhook portal.
  </Step>

  <Step title="Add your endpoint">
    Enter your HTTPS URL, choose which event types it should receive (or all), and save. The portal shows the endpoint's **signing secret** — copy it into your service.
  </Step>

  <Step title="Watch it work">
    The portal lists every delivery attempt with the response your endpoint returned, lets you **replay** any message, and can send a test event.
  </Step>
</Steps>

## What you receive

Payloads are deliberately thin — the event type and the record's id, never the record itself — so no clinical or personal data leaves PracticeHub through a webhook. Fetch the record from the API with your own key:

```json theme={null}
{
  "id": "0f7a5c4e-8f0d-4b2c-9c9d-1a2b3c4d5e6f",
  "type": "appointments.cancelled",
  "occurred_at": "2026-08-15T12:00:00+00:00",
  "data": { "entity": "appointments", "id": 4821 }
}
```

```bash theme={null}
curl https://your-clinic.your-region.practicehub.io/v3/api/appointments/4821 -H "Authorization: Bearer $KEY"
```

`id` is unique per event — use it to de-duplicate, since a delivery may be retried after a timeout. Events for one record are not guaranteed to arrive in order; the record you fetch is always current.

## Event types

Every resource below emits `{resource}.created`, `{resource}.updated` and `{resource}.deleted` (a delete or void). The full, current list — with the semantic appointment events — is on **Developers → Webhooks** and in the portal when you add an endpoint.

| Resource                                                                                               | Extra events                                                                                          |
| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| `appointments`                                                                                         | `appointments.rescheduled` (start or end changed), `.cancelled`, `.missed`, `.arrived`, `.reinstated` |
| `patients`, `payments`, `payment_allocations`, `invoices`, `files`                                     | —                                                                                                     |
| `locations`, `practitioners`, `appointment_types`, `modalities`, `payment_methods`, `referral_sources` | —                                                                                                     |
| `addresses`, `numbers`, `patient_logs`, `patient_log_tags`, `practitioner_availabilities`              | —                                                                                                     |

Several saves to one record within a single operation are reported once. A `patients.deleted` or `payments.deleted` means the record was soft-deleted or voided — it will no longer be returned by the API.

## Verifying signatures

Every delivery is signed. Verify it before trusting it — the signature scheme is [Standard Webhooks](https://www.standardwebhooks.com/), so use the library for your language with the endpoint's signing secret from the portal:

```js theme={null}
import { Webhook } from "standardwebhooks";

const wh = new Webhook(process.env.PRACTICEHUB_WEBHOOK_SECRET);
const event = wh.verify(rawBody, {
  "webhook-id": req.headers["webhook-id"],
  "webhook-timestamp": req.headers["webhook-timestamp"],
  "webhook-signature": req.headers["webhook-signature"],
});
```

Rotate the secret from the portal at any time.

## Delivery and retries

Respond with any `2xx` within a few seconds — do the work afterwards. Non-`2xx` responses and timeouts are retried with exponential backoff over roughly a day; an endpoint that keeps failing is disabled and you are notified in the portal, where you can re-enable it and replay what was missed.
