Webhooks
Try it on a test key
curl -s "https://api.wellwardhealth.com/v1/webhooks" -H "Authorization: Bearer $WELLWARD_KEY"
Register an HTTPS endpoint (dashboard → Webhooks, or POST /v1/webhooks) and choose events. You get a signing secret once — store it.
Events
| Event | When |
|---|---|
| request.completed | A price request finished — outcome says whether a price was obtained, price_ids lists them |
| request.expired | A price request passed its promised date by a week unresolved |
| price.withdrawn | A price your account retrieved in the last 30 days was withdrawn |
| price.updated | A price your account retrieved in the last 30 days changed |
| billing.payment_failed | We could not collect an invoice |
| usage.threshold_reached | Your month's charges crossed 50%, 80% or 100% of your spending cap |
price_request.completed and price.retired are the older names of request.completed and price.withdrawn; a subscription to either name still works.
An endpoint created with a test key (or as test in the dashboard) receives only test-mode events.
Payload
{ "id": "<delivery id>", "event": "request.completed", "created_at": "2026-09-23T15:04:05Z", "data": { … } }
Verify the signature
Every delivery is signed with your secret, twice, with the same MAC — verify either:
Wellward-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>X-Wellward-Signature: v=<unix seconds>,d=<hex HMAC-SHA256>
The MAC is over <timestamp>.<raw request body>. Reject a timestamp more than five minutes old.
Node
import crypto from "node:crypto";
export function verify(secret, rawBody, header) {
const m = /^t=(\d+),v1=([0-9a-f]+)$/.exec(header ?? "");
if (!m) return false;
if (Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest();
const given = Buffer.from(m[2], "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}
Python
import hmac, hashlib, time, re
def verify(secret: str, raw_body: bytes, header: str) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]+)", header or "")
if not m or abs(time.time() - int(m.group(1))) > 300:
return False
expected = hmac.new(secret.encode(), m.group(1).encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, m.group(2))
Delivery
At least once. Answer 2xx within 10 seconds; anything else is retried after 1 m, 5 m, 30 m, 2 h, 6 h and 15 h — about a day in all. Deliveries go out on a five-minute cycle, so the first attempt and each retry can land up to five minutes after that. Be idempotent on X-Wellward-Delivery (the delivery id). The dashboard shows each endpoint's delivery log, and Send test event delivers a signed webhook.test immediately (and retries it like any event if your endpoint fails).
A test endpoint receives only test-mode events, which in practice means request.completed (from test price requests) and webhook.test. Withdrawals, price updates and billing events happen only in live mode; to exercise withdrawal handling, use GET /v1/prices/changes?test_scenario=withdrawn_since. price.created can be subscribed to but is never sent.
API reference · Dashboard · Questions: hello@wellwardhealth.com