Webhooks
Set up real-time event notifications
Webhooks
A webhook is the delivery of data to an endpoint when an event occurs. In our case that event is a survey response being submitted. Webhooks are intended for advanced users with programming knowledge.
How to connect
To set up webhooks, go to the Integrations tab → Webhooks and click Connect.
Download the markdown version of the "Webhooks" section for use in ChatGPT / other LLMs:
Webhook configuration UI
Step 1: Navigate to integrations
To set up webhooks, go to the Integrations tab → Webhooks and click Connect.
Step 2: Webhook settings form
A screen will open where you need to enter the webhook name, callback URL and request type.
Step 3: Testing the webhook
You can test the webhook using https://webhook.site
Step 4: Viewing logs
Or by viewing the logs in the SurveyNinja interface.
Webhook settings
The webhook settings form has the following parameters:
| Parameter | Description |
|---|---|
| Name | A custom name for identifying the webhook in the list |
| URL | Your endpoint URL. SurveyNinja sends a POST request to it for every new survey response |
| Headers | Custom HTTP headers in "key: value" format. Sent with every request and duplicated in the headers payload body field for easy logging |
| Enabled | Activity toggle. A disabled webhook does not send requests but retains all settings |
Secret token via header
Add an Authorization: Bearer your-secret header — your server can then verify that the request genuinely came from SurveyNinja.
When webhooks fire
In SurveyNinja, webhooks fire when a survey response is submitted. This is the main event you can track.
Event: Survey response
The webhook fires every time a respondent submits an answer to your survey, giving you real-time notifications of new responses.
Automatic delivery
The webhook is sent automatically as soon as a survey response is received
Payload format
SurveyNinja sends a POST request with a JSON body. The complete request body structure is described below.
Root payload fields
| Field | Type | Description |
|---|---|---|
| headers | object | Headers configured in the webhook. Duplicated in the body for easy logging and debugging |
| extra_params | object / array | Hidden variables and UTM tags passed during the survey. Empty array [] if no variables |
| promo_code | string / null | Promo code used during the survey, or null |
| log_id | int | Log record ID for this webhook delivery |
| score_all | int | Maximum possible score for the entire survey. 0 if scoring is not configured |
| score_earned | int | Total score earned by the respondent |
| score_percent | float / null | Result percentage: (score_earned / score_all) × 100, rounded to 1 decimal place. null if score_all = 0 |
| scores | object | Score summary: fields user, max and by_question — a map { "uuid": int | null } |
| {question_uuid} | object | Answer to the question with this UUID. Each question in the survey has a separate top-level key |
Question object fields
Each question in the payload is an object keyed by the question UUID. All question types share the following common fields:
| Field | Type | Description |
|---|---|---|
| title | string | Question text (widget title) |
| type | string | Widget type: choiceSingle, choiceMultiple, rating, matrix and others. |
| question_uuid / uuid | string | Question UUID (duplicated inside the object) |
| results | object / array | Respondent's answer. Format depends on widget type — see section below |
| score_count | int / null | Score earned for this question. null if scoring doesn't apply to this type |
| score_max | int / null | Maximum score for this question. null if scoring doesn't apply |
| is_correct_question | bool / null | Overall answer correctness: true — correct, false — incorrect, null — not applicable |
| other | string | Text of the 'Other' option, if selected (for choice widgets). Empty string if not selected |
| inline_group_id | string / null | Question group UUID, if the question belongs to a group |
| inline_group_title | string / null | Question group name |
| is_inline_group_child | bool | true if the question is inside a group |
Format of results by widget type
The results field has a different format depending on the question type.
choiceSingle, choiceMultiple, yesno, dropdown
Array of selected options. Each element contains the option text, score and correctness flag.
{ "results": [ { "result": "Option A", "score": 10, "is_correct": true } ] } choiceMedia
Array of selected media options. The result field contains the URL of the selected option image.
{ "results": [ { "result": "https://app.surveyninja.io/.../image.png", "score": 3, "is_correct": false } ] } ranking
Array of options in the order set by the user (first element = ranked 1st). Scoring and correctness don't apply to this type.
{ "results": [ { "result": "First place", "score": null, "is_correct": null }, { "result": "Second place", "score": null, "is_correct": null }, { "result": "Third place", "score": null, "is_correct": null } ] } rating
Object (not array). Field is_star: true — stars/hearts mode; score_num: false — non-numeric display mode.
{ "results": { "result": "4", "is_star": true, "score": 0, "score_num": false, "is_correct": false } } scale
Object with the selected scale value, score and correctness.
{ "results": { "result": "6", "score": 0, "is_correct": true } } slider
Object with value only. Scoring doesn't apply to sliders.
{ "results": { "result": "45" } } input, email, phone
Object with the entered value. Scoring doesn't apply. For multiline input, line breaks are preserved.
{ "results": { "result": "User's answer text" } } datetime
Object with a date/time value. The value format depends on the widget mode.
// Date + time { "results": { "result": "12.03.2026 21:12" } } // Date only { "results": { "result": "11.03.2026" } } // Time only { "results": { "result": "18:14" } } file
Array of uploaded files. Each element contains a download URL and file extension.
{ "results": [ { "result": "https://app.surveyninja.io/.../document.pdf", "file_ext": "pdf" } ] } matrix
The most complex type. Contains two representations: results — human-readable view: nested object { "row": { "column": { result, score, is_correct } } }. Value result: "1" — cell is checked, result: "" — empty. answers — machine view by UUID: { "row_uuid": { "col_uuid": true | false } }
{ "results": { "Pasta": { "Bad": { "result": "1", "score": 2, "is_correct": false }, "OK": { "result": "", "score": null, "is_correct": false }, "Good": { "result": "", "score": null, "is_correct": true } }, "Potatoes": { "Bad": { "result": "1", "score": 2, "is_correct": true }, "OK": { "result": "", "score": null, "is_correct": true }, "Good": { "result": "", "score": null, "is_correct": false } } }, "answers": { "row-uuid-1": { "col-uuid-1": true, "col-uuid-2": false, "col-uuid-3": false }, "row-uuid-2": { "col-uuid-1": true, "col-uuid-2": false, "col-uuid-3": false } }, "is_bool": true } Full payload example
A real example of a webhook request body with scoring, multiple question types and custom headers. UUIDs are shortened for readability.
{ "headers": { "Authorization": "Bearer my-secret-token", "X-Source": "surveyninja" }, "extra_params": { "utm_source": "email", "utm_campaign": "spring2024" }, "promo_code": null, "log_id": 1916713, "score_all": 52, "score_earned": 29, "score_percent": 55.8, "scores": { "user": 29, "max": 52, "by_question": { "uuid-choice-1": 0, "uuid-choice-2": 3, "uuid-rating-1": 0, "uuid-scale-1": 0, "uuid-matrix-1": 6 } }, "uuid-choice-1": { "title": "Select one option", "type": "choiceSingle", "question_uuid": "uuid-choice-1", "uuid": "uuid-choice-1", "results": [ { "result": "Option B", "score": 0, "is_correct": false } ], "score_count": 0, "score_max": 4, "is_correct_question": false, "other": "", "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false }, "uuid-choice-2": { "title": "Check all that apply", "type": "choiceMultiple", "question_uuid": "uuid-choice-2", "uuid": "uuid-choice-2", "results": [ { "result": "Option A", "score": 3, "is_correct": true } ], "score_count": 3, "score_max": 3, "is_correct_question": true, "other": "", "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false }, "uuid-rating-1": { "title": "Rate the service", "type": "rating", "question_uuid": "uuid-rating-1", "uuid": "uuid-rating-1", "results": { "result": "4", "is_star": true, "score": 0, "score_num": false, "is_correct": false }, "score_count": 0, "score_max": 2, "is_correct_question": false, "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false }, "uuid-scale-1": { "title": "On a scale of 1 to 10", "type": "scale", "question_uuid": "uuid-scale-1", "uuid": "uuid-scale-1", "results": { "result": "6", "score": 0, "is_correct": true }, "score_count": 0, "score_max": 3, "is_correct_question": true, "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false }, "uuid-input-1": { "title": "Leave a comment", "type": "input", "question_uuid": "uuid-input-1", "uuid": "uuid-input-1", "results": { "result": "Everything is great!" }, "score_count": null, "score_max": null, "is_correct_question": null, "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false }, "uuid-file-1": { "title": "Attach a file", "type": "file", "question_uuid": "uuid-file-1", "uuid": "uuid-file-1", "results": [ { "result": "https://app.surveyninja.io/.../document.pdf", "file_ext": "pdf" } ], "score_count": null, "score_max": null, "is_correct_question": null, "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false }, "uuid-matrix-1": { "title": "Rate the dishes", "type": "matrix", "question_uuid": "uuid-matrix-1", "uuid": "uuid-matrix-1", "results": { "Pasta": { "Bad": { "result": "1", "score": 2, "is_correct": false }, "Good": { "result": "", "score": null, "is_correct": true } }, "Potatoes": { "Bad": { "result": "1", "score": 2, "is_correct": true }, "Good": { "result": "", "score": null, "is_correct": false } } }, "answers": { "row-uuid-1": { "col-uuid-1": true, "col-uuid-2": false }, "row-uuid-2": { "col-uuid-1": true, "col-uuid-2": false } }, "is_bool": true, "score_count": 6, "score_max": 6, "is_correct_question": false, "inline_group_id": null, "inline_group_title": null, "is_inline_group_child": false } } Backward compatibility
Scoring fields (score_max, is_correct_question, is_correct inside results) are added on top of the base format. Existing fields are not removed or renamed.
Testing webhooks
You can use the following tools to test webhooks:
webhook.site
Recommended service for testing webhooks
https://webhook.site Go to the site, get a unique URL and use it to test your webhooks.
Viewing logs
You can verify webhook operation via logs
The SurveyNinja interface provides webhook delivery logs to track their operation.
Security
For webhook security, it is recommended to use HTTPS and verify request authenticity.
Security recommendations
- Use HTTPS for the callback URL
- Verify request signatures to confirm their authenticity
- Restrict endpoint access to SurveyNinja IP addresses only
- Implement timeouts for webhook processing