Overview
Webhooks send an HTTPPOST 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.
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 asigning_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.Managing webhooks
UseGET /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.

