Agent skill

Sohar Health Webhooks Skill

Receive and verify Sohar Health webhooks (insurance eligibility verification and patient intake API for behavioral health, delivered via Svix). Use when setting up Sohar Health webhook handlers, debugging svix-signature verification, or handling verification.updated, verification.pending, discovery.updated, flow.updated and flow.callback events.

Install this skill

npx skills add hookdeck/webhook-skills --skill sohar-health-webhooks


Sohar Health 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.

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
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/, examples/nextjs/, 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, 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:

EventFires whenBody fields
verification.updatedA production verification completedverificationId, id, status (e.g. complete.eligible)
verification.pendingA production verification was not resolved immediatelyverificationId, id, status (pending)
discovery.updatedA discovery request was updateddiscoveryId, id, status
flow.updatedA Flow Run's status or state changedflowRunId, flowId, status, stateId
flow.callbackA Flow Run is waiting for additional inputflowRunId, input (a JSON Schema)
sandbox.verification.updated, sandbox.verification.pending, sandbox.discovery.updated, sandbox.flow.updated, sandbox.flow.callbackThe same, for sandboxSame 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 for payloads and delivery rules.

Environment Variables

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

Local Development

# 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.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →