Skip to main content

Analytics – Your reports update on their own: send every Survio response to Google Sheets

Send every completed Survio response to a Google Sheet automatically, then build a live chart or a pivot table on top. Step-by-step guide with n8n and a ready-made workflow to download.

Written by Success Team Survio

Which case is for you?

Case A – simple, for everyone: one chart of your answers that updates with every new response.

Case B – for teams that compare groups: a pivot table you can filter by channel, language or any other column.

Every response your survey collects lands as a new row in a Google Sheet the moment it's submitted. Build a chart or a pivot table on top of it once, and your report stays current on its own: no exports, no copying, no waiting for someone to prepare the numbers.
​

This is the report you get in Case A, a chart that updates with every new response:
​

The report tab with response counts and a column chart


​

And this is the pivot table in Case B, filtered by channel with a slicer:
​

Pivot table with a slicer on the Channel column

How this works. Survio sends each completed response to a webhook URL. An automation tool (we use n8n in this guide) receives it and writes one row into your sheet. Survio does the sending, n8n does the writing, and the sheet does the reporting.

New to webhooks in Survio? Start with Webhooks: send responses to your other tools in real time, then come back here.
​

What you'll build

Both reports are fed by the same setup: every completed response becomes one row on a tab called responses.

At a glance

Case A: one chart

Case B: report by segment

Result

A chart of how the answers are distributed, updated with every new response

A pivot table you can filter by channel, language or any other column

Tools

Survio, n8n, Google Sheets

Survio, n8n, Google Sheets (pivot table and slicer)

Best for

A small business that wants one simple chart that is always current

A team that compares groups, for example online and in-store customers

Before you start

  • A Survio survey on a paid plan. Webhooks aren't available on the Free plan, and each plan allows a set number of webhooks per survey (see the webhooks article).

  • An n8n account, n8n Cloud or self-hosted.

  • A Google account for Google Sheets.

In this guide we use a short customer satisfaction survey: a star rating, an "Overall, how satisfied are you?" choice question, a "How did you shop with us this time?" choice question, an open comment and an optional e-mail question. Your survey can look different; you map your own questions in Step 5.
​

Step 1 — Prepare the sheet

  1. Create a new Google Sheet and rename its first tab to responses.

  2. In row 1, type the column headers. We use Response ID, Submitted, Rating (1-5), Satisfaction, Channel, What could we do better, E-mail, Order ID, Language and Source.

Keep Response ID as the first column. It's Survio's unique id for each response. n8n uses it to update the existing row instead of adding a duplicate when a delivery is retried.

Step 2 — Create the webhook in n8n

  1. In n8n, create a new workflow and add a Webhook node as the trigger.

  2. Set HTTP Method to POST.

  3. Switch to the Production URL tab and copy the address.

  4. Click Publish. The production URL only accepts deliveries once the workflow is published.

n8n Webhook node with HTTP Method POST and the Production URL tab
Published button in the n8n editor

Use the Production URL, not the Test URL. The Test URL only listens while the editor is waiting for a test event, so Survio's deliveries fail as soon as you close it. Survio retries a failed delivery for about a day, and after 5 days of failures it switches the webhook off.

Step 3 — Connect the survey in Survio

  1. Open your survey and go to Get responses.

  2. In the left sidebar under Automations & Integrations, select Webhooks and click + Add a webhook.

  3. Paste the n8n Production URL into Webhook URL and click Save webhook. The webhook is switched on straight away.

Add a webhook dialog in Survio with the n8n Production URL filled in
Saved webhook in Survio, switched on


​

Step 4 — Send a test response and find your question ids

  1. Open your survey through its normal link and submit one complete response. Survio sends only completed responses, so click through to the end.

  2. In n8n, open the Executions tab. The delivery arrives within a few seconds.

  3. Open the execution and click the Webhook node. Expand body, then data, then answers. Each answer shows its question.id, the question label and the answer.

  4. Write down the id of each question you want as a column. You can also click Pin to keep building against this response without submitting the survey again.

Executions tab in n8n with successful runs
Webhook node output in n8n: body, data, answers

Always pick answers by question id. Answers arrive as a list. If you later add a question to the survey, positions in the list shift and your columns would quietly fill with the wrong answers. Question ids never change.

Looking for the respondent's e-mail? Don't use data.email: it stays empty when the e-mail comes from an e-mail question or a URL parameter. Read it from the answer to your e-mail question, or from data.parameters.

Step 5 — Turn each response into a row

This takes two nodes: Prepare row picks the answers out of the response, and Save response to sheet writes them into your sheet.

Prepare row

  1. Add an Edit Fields (Set) node after the Webhook node and rename it to Prepare row.

  2. Under Fields to Set, add one field for each column of your sheet and keep its type String. The field name must match the column header exactly; the value is the expression below.

  3. Replace the question ids (90403153 and so on) with the ids you wrote down in Step 4, and Europe/Prague with your time zone.

Field name

Value

Response ID

{{ $json.body.data.responseId }}

Submitted

{{ DateTime.fromISO($json.body.data.submitted).setZone('Europe/Prague').toFormat('yyyy-MM-dd HH:mm') }}

Rating (1-5)

{{ $json.body.data.answers.find(a => a.question.id === 90403153)?.answer ?? "" }}

Satisfaction

{{ $json.body.data.answers.find(a => a.question.id === 90403151)?.answer?.labels?.[0] ?? "" }}

Channel

{{ $json.body.data.answers.find(a => a.question.id === 90403154)?.answer?.labels?.[0] ?? "" }}

What could we do better

{{ $json.body.data.answers.find(a => a.question.id === 90403152)?.answer ?? "" }}

E-mail

{{ $json.body.data.answers.find(a => a.question.id === 90403187)?.answer || $json.body.data.parameters?.email || "" }}

Order ID

{{ $json.body.data.parameters?.['order-id'] ?? '' }}

Language

{{ $json.body.data.langCode }}

Source

{{ $json.body.data.source }}

Prepare row node in n8n with Fields to Set and their expressions


​

Save response to sheet

  1. Add a Google Sheets node after Prepare row, rename it to Save response to sheet and choose the operation Append or Update Row.

  2. Connect your Google account, then pick your spreadsheet under Document (from the list, or paste its ID with By ID) and the responses tab under Sheet.

  3. Set Mapping Column Mode to Map Automatically and Column to match on to Response ID.

Google Sheets node: Append or Update Row, Map Automatically, Column to match on Response ID

Why a separate Prepare row step? When you pick a different spreadsheet or tab in the Google Sheets node, n8n clears the column mapping inside that node. With the expressions in Prepare row, nothing is lost: the Google Sheets node only matches field names to column headers.

Click Publish again. Changes you make after publishing go live only when you publish again.

n8n workflow: Survio Webhook, Prepare row, Save response to sheet

A few details behind those expressions:

  • Choice questions arrive as { ids, labels, value }. labels[0] is the text of the selected option. For a multiple-choice question, use labels.join(', ') to put all selected options in one cell. value holds the text typed into an "Other" option.

  • Times arrive in UTC. The Submitted expression converts them to your time zone, and Google Sheets then treats the column as a date and time you can sort and group by.

  • URL parameters with a hyphen in their name, such as order-id, need the bracket form parameters['order-id']. Survio doesn't allow underscores in parameter names.

  • The survey title isn't in the data Survio sends. If several surveys write into one sheet, add a Survey column to the sheet and a Survey field with the survey's name to Prepare row.

Submit another response. A new row appears in the sheet within seconds.

The responses tab in Google Sheets with one row per response


​

Case A — Chart the answers

  1. Add a tab named report.

  2. Type Satisfaction in A1 and Responses in B1. In A2 to A6, list the answer options exactly as they appear in your survey.

  3. In B2, enter =COUNTIF(responses!D:D,A2) and copy it down to B6.

  4. Select A1:B6 and choose Insert → Chart. Google Sheets suggests a column chart. To change its title, go to Customize → Chart & axis titles.

Each new response adds a row to responses, and the counts and the chart update on their own, like the chart at the top of this article.
​

Case B — Compare groups with a pivot table

  1. On the responses tab, select the table and choose Insert → Pivot table, then New sheet.

  2. Under Rows, add Satisfaction. Under Values, add Response ID and change Summarize by from SUM to COUNTA.

  3. Choose Data → Add a slicer and pick the Channel column. Select the values you want to see and confirm with OK. The result looks like the pivot table at the top of this article.

You can slice by any column: Channel (your own question), Language (the language version of the survey the respondent answered in), Source (how the response came in, for example a direct link or an e-mail invitation) or Order ID.

A slicer selection isn't kept for the next visit. When the file is opened again, the slicer shows all values. To keep a selection, save it as the default in the slicer's menu.

Get the ready-made workflow

Instead of building Steps 2 and 5 by hand, start from our workflow. Download the file survio-responses-to-google-sheets.json from the attachments at the end of this article, then:

  1. In n8n, create an empty workflow. Open the menu (three dots) next to the workflow name and choose Import, then From file. Pick the file you downloaded.

  2. Open Prepare row and replace the question ids with your own and Europe/Prague with your time zone.

  3. Open Save response to sheet. It shows a red warning sign until it's set up. Connect your Google account and pick your spreadsheet and its responses tab. n8n then switches Mapping Column Mode to Map Each Column Manually: set it back to Map Automatically and set Column to match on to Response ID.

  4. Open Survio Webhook, copy the Production URL into Survio (Step 3) and click Publish.

n8n workflow menu with Import and From file
Imported workflow: Survio Webhook, Prepare row, Save response to sheet

Test it end to end

  1. Submit a response through the survey link. A row appears on the responses tab within seconds.

  2. Check that Response ID is filled in. That's what prevents duplicates.

  3. Look at the Executions tab in n8n. Every delivery and its data are listed there.

Troubleshooting

Symptom

Cause

Fix

Nothing arrives in n8n

The Test URL is in Survio, or the workflow isn't published

Copy the Production URL into Survio and click Publish

Survio won't save the webhook URL from a self-hosted n8n

n8n shows an internal address instead of its public one

Set n8n's WEBHOOK_URL setting to your public https address and copy the URL again

"Listen for test event" shows nothing although the survey was submitted

Survio sends to the Production URL; listening watches the Test URL

Check the Executions tab instead

Your latest change has no effect

The workflow is published, but the change isn't (the Publish button shows a dot)

Click Publish again

The same response appears twice

Operation set to Append Row, or no column to match on

Use Append or Update Row and match on Response ID

A column stays empty and a new column appears at the end of the sheet

A field name in Prepare row doesn't match the column header exactly

Rename the field in Prepare row to match the header, then delete the extra column

A cell shows [object Object]

A choice answer mapped without labels

Use ?.answer?.labels?.[0]

Columns fill with the wrong answers after you edit the survey

Answers picked by their position in the list

Pick them by question.id

The E-mail column stays empty

Mapped from data.email

Read the e-mail from the e-mail question's answer or a URL parameter

Responses from before the setup are missing

Webhooks only send responses submitted after the webhook was created

Export the older responses once from Survio and paste them in

Survio switched the webhook off

The webhook URL failed for 5 days in a row

Fix the cause, then switch the webhook back on in Survio

Good to know

  • Survio sends only completed responses, one message per response, within seconds.

  • Nothing is sent retroactively: not responses from before the webhook existed, and not responses from while it was paused.

  • A failed delivery is retried up to 8 times over roughly 27 hours, and each attempt waits up to 30 seconds for a reply. Matching on Response ID makes retries harmless.

  • If deliveries fail for 5 days in a row, Survio switches the webhook off and you need to switch it back on.

  • Pausing a webhook keeps its slot; deleting it is permanent.

  • Survio doesn't keep a delivery history in the app. The Executions tab in n8n is your log.

  • Sending responses to webhooks doesn't use up any extra responses from your plan.

Rather have us set it up? On the Webhooks page in Survio, click Get expert setup, tell us what you'd like to happen, and our team will build it, test it and hand you a working webhook.

Did this answer your question?