Webhooks
Webhooks let UserSound notify your server when a respondent finishes a survey.
Use them to unlock a discount, update CRM status, issue a reward, or trigger any
follow-up in your product when interview_status becomes complete.
Enable webhooks
- Open your project in the UserSound dashboard
- Click Setup
- Open the Webhooks section (below Widget settings)
- Turn on Enable completion webhook
- Enter your HTTPS endpoint URL
- Click Save
Saving generates a signing secret automatically. Copy it from the dashboard — your server needs it to verify requests. Use Rotate secret if you need a new one.
Click Send test event to POST a sample webhook.test payload to your endpoint.
Identify respondents
For unlock or reward flows, pass an identity when the survey opens so the webhook can tell you which user finished. Use either:
external_user_id(preferred) — your stable account / customer IDemail— if that is already how you identify users
Your app should inject these from the authenticated session. Do not let end users edit them in the URL.
Survey link
https://usersound.com/survey/YOUR_PROJECT_UUID/?external_user_id=YOUR_USER_ID
https://usersound.com/survey/YOUR_PROJECT_UUID/[email protected]
Embed / widget
Pass identity in the SDK context object. See
Identifying users
for the full embed pattern (user_id maps to the same identified respondent as external_user_id).
Events
| Event type | When it fires |
|---|---|
interview.completed |
Once, when a response transitions to complete. |
webhook.test |
When you click Send test event in Setup. |
Delivery happens as soon as the interview is marked complete — it does not wait for summary generation.
Each completed response produces at most one interview.completed delivery.
Payload
Example interview.completed body:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "interview.completed",
"created_at": "2026-07-22T18:00:00.123456+00:00",
"data": {
"project_uuid": "05de077e-5f64-4b61-bc8b-4fc54ede38cc",
"response_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"interview_status": "complete",
"external_user_id": "customer-123",
"respondent": {
"email": "[email protected]",
"user_name": "Alex Example",
"first_name": "Alex"
},
"completed_at": "2026-07-22T18:00:00.120000+00:00"
}
}
| Field | Description |
|---|---|
id |
Unique event / delivery ID. Use for idempotency on your side. |
type |
Event name, e.g. interview.completed. |
data.project_uuid |
The UserSound project UUID. |
data.response_id |
The interview response UUID. |
data.external_user_id |
Your user ID if one was passed; otherwise null. |
data.respondent |
Optional profile fields stored on the identified respondent. |
Request headers
| Header | Description |
|---|---|
Content-Type |
application/json |
User-Sound-Event |
Same as the payload type (e.g. interview.completed). |
User-Sound-Delivery-Id |
Same as payload id. |
User-Sound-Signature |
t=<unix_timestamp>,v1=<hmac_sha256_hex>.
HMAC-SHA256 of {timestamp}.{raw_body} using your signing secret —
verify this before trusting the payload.
|
Completion status API
If the user returns to your app before the webhook arrives, poll this endpoint from your backend. Authenticate with the same signing secret as a Bearer token.
GET https://usersound.com/api/v1/survey/YOUR_PROJECT_UUID/completion-status/?external_user_id=YOUR_USER_ID
Authorization: Bearer YOUR_WEBHOOK_SECRET
{
"external_user_id": "customer-123",
"email": null,
"completed": true,
"response_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"completed_at": "2026-07-22T18:00:00.120000+00:00"
}
- Provide exactly one of
external_user_idoremail. - Call this from your server, not a public mobile/browser client that exposes the secret.
- If the user already completed and no new webhook fires, this endpoint still returns
completed: true.
Retries
- If your endpoint is down or returns an error, UserSound retries a few times with short delays.
- Return a successful HTTP response (status code 200–299) once you have handled the event.
- The same completion may be delivered more than once — store the event
id(orresponse_id) and ignore duplicates so you do not unlock a reward twice.
Production webhook URLs must use https:// and resolve to public addresses.
Private / localhost URLs are rejected in production.
Testing locally
- Point the webhook URL at a local HTTP receiver, or use a tunnel like ngrok for HTTPS.
- Save, copy the signing secret, then click Send test event.
- To test a real completion, open the survey with
?external_user_id=...and finish the interview.