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

# Outbound webhooks

> Subscribe to Partner API events, store the webhook secret, verify HMAC signatures, and handle retries idempotently.

OnboardMe POSTs JSON to your HTTPS URL when something happens in the practice. Use this instead of polling for workflow events.

## 1. List event keys

`GET /api/v1/webhooks/events` returns the live event list (for example `proposal.accepted` and `eform.submitted`). Do not hard-code a guessed set.

## 2. Subscribe

Write access required. **One subscription per event**. Repeat the call with the same URL to cover multiple events.

`POST /api/v1/webhooks` with your HTTPS `url` and `event`.

**201** includes `id` and `webhookSecret`. Store the secret immediately. List and get routes do **not** return it again.

`GET /api/v1/webhooks` lists subscriptions (no secrets). `POST /api/v1/webhooks/{id}/test` sends a test delivery. `DELETE /api/v1/webhooks/{id}` deactivates the subscription.

## 3. Verify every delivery

Each POST to your URL includes:

| Header           | Meaning                               |
| ---------------- | ------------------------------------- |
| `X-Om-Event`     | Event key                             |
| `X-Om-Event-Id`  | Unique id. Use as an idempotency key. |
| `X-Om-Timestamp` | Unix seconds (UTC) used when signing  |
| `X-Om-Signature` | `sha256=` HMAC (hex, lowercase)       |

Verification:

1. Read the **raw** body bytes. Do not re-serialize JSON.
2. Build `canonical = {X-Om-Timestamp} + "." + {rawBody}`.
3. Compute `expected = "sha256=" + HMAC_SHA256_UTF8(webhookSecret, canonical)` (hex lowercase).
4. Compare with a constant-time compare. Reject on mismatch.

Return **2xx** quickly. Do slow work asynchronously.
