Skip to main content

Overview

Webhooks send an HTTP POST to your hook_url when subscribed events occur. Choose interview.completed, study.completed, report.ready, study.paused, study.resumed, and/or study.stopped in event_types. Omit study_id for an account-wide webhook, or include an owned study_id to receive events for one study. The default subscription is interview.completed. Each subscribed event uses an envelope with id, type, study_id, and data. For interview.completed, data has the same public shape returned by Get Interview, including the participant, quality, messages, recording links, and screener responses.
1

Register a webhook

Call Create Webhook with the hook_url that should receive events, with optional study_id and event_types. The response includes a signing_secret — store it securely to verify signatures. Send an Idempotency-Key to recover the same response after a timeout without registering a second webhook. The same-key response can be replayed for 30 days while the webhook and secret remain current; ordinary reads hide the secret.
2

Receive interview data

Your endpoint receives a signed POST when a subscribed event occurs. The interview payload is described below; study and report events use smaller data objects.
3

Stop receiving data

Delete the webhook by its id to stop further deliveries. The URL-based delete endpoint remains available for account-wide registrations.
List and get operations return webhook IDs, scopes, event types, and URLs without revealing stored signing secrets. Use secret rotation when the original secret is lost or needs replacement; the new secret appears only in the rotation response.

Delivery behavior

Return 2xx promptly and deduplicate using the stable event id: a timed-out delivery may have reached your receiver before it is retried. Inspect /webhooks/{webhook_id}/deliveries for status, timing, and coarse failure reasons. The history does not store payloads.

Payload

Subscribed events use {id, type, study_id, data}. For interview.completed, data is the completed interview in the same public shape as GET /interviews/{id}. The participant is nested under data.participant.
study.completed is emitted when its active target cycle reaches its target. Each newly persisted report has its own report.ready event ID.
The study.completed envelope has the same top-level fields, with a cycle ID as id and data.completed_at instead of data.report_id. A test ping uses {"type":"webhook.test","id":"webhook-id"} and contains no participant or study data. Test pings use the same signing headers and appear in delivery history. Public API pause, resume, and stop calls emit study.paused, study.resumed, and study.stopped after the transition succeeds. Their data contains only study_id and the resulting fielding_status. Each transition receives a new event ID; deduplicate redeliveries by that ID. These lifecycle events currently cover the public API routes, not dashboard transitions.

Fields

Anonymous panel interviews have no identifiable participant, so participant is null for them. Internal columns (transcripts of the raw call, internal IDs, and the like) are never included — the webhook delivers exactly the public interview shape.

Authentication

Every delivery is signed so you can verify it genuinely came from User Intuition. When you register a webhook, the response includes a signing_secret (prefixed whsec_, shown once). Each request carries two headers:

Verifying a signature

Recompute the HMAC over "<timestamp>.<raw request body>" using your stored signing_secret and compare it to X-UI-Signature in constant time. Use the raw request body bytes — do not re-serialize the parsed JSON, or the signature won’t match.
1

Verify the signature

Reject any request whose X-UI-Signature doesn’t match your recomputed HMAC.
2

Check the timestamp

Reject requests with an X-UI-Timestamp outside a small window (e.g. 5 minutes) to prevent replay attacks.
3

Use HTTPS

Always register an https:// URL so the payload is encrypted in transit.
Store the signing_secret securely — it is shown only once, at creation. If you lose it or need to rotate it, call the secret-rotation endpoint and update your receiver with the new secret. Legacy webhooks created before signing was introduced are delivered without signature headers.

Managing webhooks

Use GET /api/public/v1/webhooks/ to find webhook IDs and GET /api/public/v1/webhooks/{webhook_id} to inspect one registration. Neither response includes its stored signing secret. Use POST /api/public/v1/webhooks/{webhook_id}/rotate-secret to replace a lost or compromised secret; update the receiver with the new one immediately. Use POST /api/public/v1/webhooks/{webhook_id}/test to send a signed, data-free test event. Check GET /api/public/v1/webhooks/{webhook_id}/deliveries for recent attempts (page and page_size are bounded). Delete by ID with DELETE /api/public/v1/webhooks/{webhook_id}. Repeating the delete returns 404. The older DELETE /api/public/v1/webhooks/ operation accepts a JSON body and is deprecated. New integrations should use the ID route.

Create Webhook

Register a URL, optional study scope, and event subscriptions.

Delete Webhook

Stop sending completed interviews to a registered webhook ID.