Skip to main content
Most of the integration is request/response. Webhooks cover everything that happens on Prescience’s side after your call returns: market rates landing for a new census, underwriting finalizing a large-group quote, a company activating to prod, an enrollment landing. Signatures follow the standard-webhooks scheme (the same one used by Svix and a growing list of providers), so you can verify with an off-the-shelf library or ~10 lines of code.

Setup

Register your HTTPS endpoint. It must be a public https:// URL whose hostname resolves only to public addresses: loopback, private, link-local and internal hostnames or addresses (such as localhost, *.internal or 10.0.0.0/8) are rejected. Prescience checks the address again before each delivery. The signing secret comes back once; store it like an API key:
Response (200)
One endpoint and one secret per mode: your test key registers the test endpoint and your live key the live one, and changing one never touches the other. Calling PUT /webhook again with the same mode replaces the URL and issues a fresh secret; that’s also the rotation mechanism. POST /webhook/test pings the endpoint for your key’s mode. Then prove the loop works end to end:
This sends one signed ping event to your endpoint and waits for the result:
Response (200)
If your handler verifies it and returns 2xx, you’re done with setup. statusCode is your endpoint’s response, or null on a network error or timeout. The ping is listed by GET /events but is not retried automatically.

Delivery format

Every event is a POST with a JSON body and three signature headers:
The body is delivered compact (no whitespace). The signature above is the genuine HMAC of this exact example under the setup secret whsec_4f8a1c2b9d3e6f7a8b9c0d1e2f3a4b5c. Wire up your verifier against it as a unit test before touching production traffic.

Event catalog

Full payload schemas are in the API reference under Webhooks.

Verifying signatures

The signature is an HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw body}:
  1. Key: strip the whsec_ prefix from your secret and base64-decode the remainder; the resulting bytes are the HMAC key.
  2. Signed content: concatenate the webhook-id header, the webhook-timestamp header, and the raw request body, joined by .. Verify against the exact bytes you received, never a re-serialized parse.
  3. Compare: base64-encode the HMAC and compare (constant-time) against each space-separated v1,<base64> candidate in webhook-signature. Any match passes.
Reject anything that fails, and reject timestamps older than ~5 minutes to blunt replay.
The official standard-webhooks libraries (npm install standard-webhooks, pip install standardwebhooks) implement this exact verification: pass them the raw body, the headers, and your whsec_ secret.

Retries and reliability

Every event is stored before it is sent, so an event is never lost because of a failure on either side. The first attempt runs right after the change that caused it. A delivery counts as successful on any 2xx response within 10 seconds; redirects are not followed. When an attempt fails, the event is retried with the same id on this schedule after each failure: 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours. That is 8 attempts over about two days. After the last one the event is marked dead; it stays listed and you can send it again. Build your handler accordingly:
  • Return 2xx fast (under 10 seconds). Enqueue work; don’t do it inline.
  • Dedupe on event.id. Retries and redeliveries reuse the same id, so treat deliveries as at-least-once.
  • Don’t rely on ordering. A retried event can arrive after a newer one. Read the current state from the API before acting on it.
  • Don’t treat webhooks as the source of truth. They’re a prompt, not a ledger: on anything critical (or after downtime), reconcile by polling GET /groups, GET /quotes/{id}, and GET /enrollments/{id}. Every webhook-announced fact is readable from the API.

Reconciling missed events

GET /events lists the events for your key’s mode, newest first, with each one’s delivery state. Filter with ?type= and page with ?limit= and ?cursor=:
Response (200)
GET /events/{eventId} returns one event. POST /events/{eventId}/redeliver sends it again now with the same id and returns the updated delivery state; a failed or dead event also restarts its retry schedule. Events created before you registered a webhook are kept, so after setup you can list them and redeliver the ones you need.