---
title: "Survey webhooks"
description: "Receive survey responses in your backend: survey.response.completed, survey.response.answered and survey.response.partial events, payload and migration from ticket webhooks."
url: https://docs.gleap.ai/en/articles/703-survey-webhooks
collection: "Surveys"
updated: 2026-11-01
language: en
---

# Survey webhooks

Receive survey responses in your backend: survey.response.completed, survey.response.answered and survey.response.partial events, payload and migration from ticket webhooks.

Add a webhook under **Settings → Apps → Webhooks** and pick the survey events you need. Responses no longer create tickets, so survey integrations use these events instead of `ticket.created`.

## Events

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Event</p></th><th colspan="1" rowspan="1"><p>When</p></th><th colspan="1" rowspan="1"><p>Default</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>survey.response.completed</code></p></td><td colspan="1" rowspan="1"><p>A respondent reached an ending.</p></td><td colspan="1" rowspan="1"><p>On for new webhooks</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>survey.response.answered</code></p></td><td colspan="1" rowspan="1"><p>A question was answered (the response may still be in progress). High volume — one event per answer.</p></td><td colspan="1" rowspan="1"><p>Opt-in</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>survey.response.partial</code></p></td><td colspan="1" rowspan="1"><p>A response stopped without reaching an ending: no new answer for 30 minutes.</p></td><td colspan="1" rowspan="1"><p>Opt-in</p></td></tr></tbody></table>

A response sends `survey.response.partial` at most once. If the person comes back and finishes, `survey.response.completed` follows.

## Payload

Answers are keyed by each question's answer `key`, so payloads stay stable when you reword or reorder questions.

```json
{
  "event": "survey.response.completed",
  "data": {
    "response": {
      "id": "rsp_4kq9X2mT7vB1nR8sLp3Z",
      "status": "completed",
      "version": 3,
      "answers": { "nps_score": 4, "detractor_reason": "Slack sync stopped twice this week" },
      "path": ["q_nps", "q_detractor", "end_thanks"],
      "endingId": "end_thanks",
      "channel": "web",
      "fields": { "plan": "pro" },
      "language": "en",
      "pageUrl": "https://app.yourcompany.com/settings",
      "startedAt": "2026-10-06T09:12:03.000Z",
      "completedAt": "2026-10-06T09:12:41.000Z"
    },
    "survey": { "id": "q7x2kd", "name": "Post-onboarding NPS", "version": 3 },
    "contact": { "userId": "user_1842", "name": "Ada Lovelace", "email": "ada@example.com" }
  }
}
```

-   `survey.id` is the survey's SDK id — the value you pass to `Gleap.showSurvey`.
    
-   `contact` is `null` for anonymous respondents.
    
-   Answer values: numbers for scales and `number`, the choice id for `single`, an array of choice ids for `multi`, `true` / `false` for `yesno`, strings for text inputs and `YYYY-MM-DD` for dates. "Other" answers are `{ "other": "…" }`. See [Survey definition](https://gleap-developer-docs.gleap.help/en/articles/700-survey-definition#blocks).
    
-   `survey.response.answered` carries the response as it is at that moment (`status: "in_progress"`), including all answers so far.
    

## Migrating from ticket webhooks

Before Surveys 2.0, every survey response created a ticket of type Survey, and integrations listened to `ticket.created` filtered to surveys.

> **Note:** **Legacy compatibility until January 5, 2027.** Webhooks on `ticket.created` that received survey tickets before — filtered to the Survey type, or without a type filter — keep receiving a ticket-shaped payload for every completed response: `formData` keyed by the old question names (migrated surveys) or answer keys with choices as their label text, `session` with the same contact fields as before, and `surveyResponse` with the response id. Move those integrations to `survey.response.completed` before then.

The ticket-type filter of a webhook only narrows ticket and message events, so a webhook filtered to Survey also receives the `survey.response.*` events you add to it.

To act on responses inside Gleap — email the respondent, open a ticket, notify a teammate — use a [survey automation](https://gleap-developer-docs.gleap.help/en/articles/704-survey-automations).

Mapping from the old payload:

<table style="min-width: 50px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Old (<code>ticket.created</code>, type Survey)</p></th><th colspan="1" rowspan="1"><p>New (<code>survey.response.completed</code>)</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>formData.&lt;question name&gt;</code></p></td><td colspan="1" rowspan="1"><p><code>data.response.answers.&lt;answer key&gt;</code> (migrated surveys use the old question names, converted to snake_case, as keys)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>session</code></p></td><td colspan="1" rowspan="1"><p><code>data.contact</code></p></td></tr><tr><td colspan="1" rowspan="1"><p>ticket <code>outbound</code></p></td><td colspan="1" rowspan="1"><p><code>data.survey.id</code> / <code>data.survey.name</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>createdAt</code></p></td><td colspan="1" rowspan="1"><p><code>data.response.completedAt</code></p></td></tr></tbody></table>

## Related

- [Surveys](https://docs.gleap.ai/en/articles/699-surveys.md)
- [Survey definition](https://docs.gleap.ai/en/articles/700-survey-definition.md)
- [Survey analytics](https://docs.gleap.ai/en/articles/701-survey-analytics.md)
- [Surveys REST API](https://docs.gleap.ai/en/articles/702-surveys-rest-api.md)
- [Survey automations](https://docs.gleap.ai/en/articles/704-survey-automations.md)
