# Sohar Health Webhooks

[Sohar Health](https://soharhealth.com) is an insurance eligibility-verification and patient-intake
API for behavioral-health providers. It sends webhooks when a verification, discovery request or
Flow Run changes state, delivered through Svix, which implements Standard
Webhooks. Every delivery is an HTTP `POST` with a JSON body and three signature headers:
`svix-id`, `svix-timestamp`, and `svix-signature`. Bodies are flat: a top-level `event` plus the
resource id (`verificationId`, `discoveryId` or `flowRunId`), and `status` is the resource's state,
not the event type. Each production event has a `sandbox.` twin.

## When to Use This Skill

* How do I receive Sohar Health webhooks?
* How do I verify Sohar Health webhook signatures (`svix-signature`)?
* How do I handle `verification.updated` and `verification.pending` events?
* How do I respond to a `flow.callback` event that is waiting for input?
* Why is my Sohar Health webhook signature verification failing, or rejecting Hookdeck retries?

## Verification (core)

Signed content is `{svix-id}.{svix-timestamp}.{raw body}`. Svix signs it with HMAC-SHA256 using
the base64-decoded secret (the part after `whsec_`), base64-encodes the result, and sends one or
more space-separated `v1,<signature>` entries. Use the official `svix` library, which compares in
constant time. Read the `svix-*` headers, and when `svix-id` is absent fall back to the `webhook-*`
headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`), as Hookdeck does.

```javascript
import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.SOHAR_HEALTH_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes

```

```python
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["SOHAR_HEALTH_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

```

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

The webhook secret is per endpoint and lives in the Svix Portal (dashboard Webhooks > endpoint >
**Open Svix Portal**). The docs print no secret sample, but Svix secrets start with `whsec_`. Keep
the full `whsec_...` value in `SOHAR_HEALTH_WEBHOOK_SECRET`. Handlers fail closed: with the variable
unset they return 500 instead of accepting anything.

### Hookdeck retries and replays hit the 5-minute window

The `svix` library always enforces a fixed 5-minute timestamp window, and it can't be configured.
Hookdeck retries and replays resend the **original** `svix-timestamp`, so the library rejects any
retry or replay that happens more than 5 minutes after the original delivery. If you verify
behind Hookdeck (or replay old events), use the manual verifier in
[references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sohar-health-webhooks/references/verification.md), where the window can be dropped or
widened. Deduplicate on `svix-id` instead of relying on the timestamp.

## Event Types

Ten events are documented (the list may not be exhaustive, so acknowledge unknown ones with `200`).
The examples branch on the top-level `event`:

| Event | Fires when | Body fields |
| --- | --- | --- |
| `verification.updated` | A production verification completed | `verificationId`, `id`, `status` (e.g. `complete.eligible`) |
| `verification.pending` | A production verification was not resolved immediately | `verificationId`, `id`, `status` (`pending`) |
| `discovery.updated` | A discovery request was updated | `discoveryId`, `id`, `status` |
| `flow.updated` | A Flow Run's status or state changed | `flowRunId`, `flowId`, `status`, `stateId` |
| `flow.callback` | A Flow Run is waiting for additional input | `flowRunId`, `input` (a JSON Schema) |
| `sandbox.verification.updated`, `sandbox.verification.pending`, `sandbox.discovery.updated`, `sandbox.flow.updated`, `sandbox.flow.callback` | The same, for sandbox | Same as above |

No retry schedule or ordering is documented. Acknowledge with `200`, deduplicate on `svix-id`, and
fetch the full resource from the API before acting. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sohar-health-webhooks/references/overview.md) for payloads and delivery rules.

## Environment Variables

```bash
# Webhook secret from the Svix Portal (Svix format, starts with whsec_)
SOHAR_HEALTH_WEBHOOK_SECRET=whsec_xxxxx

```

## Local Development

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

```

Register the tunnel URL with the Create Webhook API (`POST /v1/webhooks`), subscribe it with Create
Event (`sandbox.verification.updated` is recommended for testing), and copy the secret from the
Svix Portal into `.env`. See [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sohar-health-webhooks/references/setup.md).

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sohar-health-webhooks/references/overview.md) - Sohar Health webhook concepts, events, payloads, delivery rules
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sohar-health-webhooks/references/setup.md) - Register an endpoint via the API, get the secret from the Svix Portal
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sohar-health-webhooks/references/verification.md) - Svix verification details, header fallback, manual verifier, timestamp window