How to Send a Slack Alert When a Google Form Gets a New Response
Summary
- Zapier's task-metered pricing starts at $19.99/month and scales with every form submission, while Apps Script integrations fail silently in production.
- Google service accounts cannot read form responses; user-consent OAuth is required with the
forms.responses.readonlyscope, authorised by the form's owner or editor. - Use the
responseIdas an idempotency key and write a delivery-status ledger back to the linked Sheet to surface silent failures and expired watches. - Deploy the complete YAML workflow below, then harden it with dynamic
questionIdmapping viaforms.getand a second Slack-event workflow for two-way updates. - Jinba Flow enables teams to define, deploy, and audit this Google Forms-to-Slack integration as a versioned YAML workflow in minutes.
Google Forms has no built-in channel for alerting a team when a response arrives. By default, submissions accumulate in a spreadsheet with no notification mechanism, requiring manual review.
The common workarounds carry their own costs. Marketplace add-ons such as SlackNotify require account-level permissions many security teams will not approve. Zapier works, but its paid plans start at $19.99 per month on the Professional tier, and every form submission consumes a task, so the bill scales directly with response volume. Custom Google Apps Script is free, but the script resides in an individual account, has no version history, and breaks silently when the form schema changes.
This article describes a fourth option: a declarative, rule-based workflow defined in YAML that connects Google Forms to Slack and is auditable, versionable, and owned by the team rather than an individual.
Why the common approaches fail in production
The Apps Script pattern warrants examination because it is the most widely shared solution. The typical implementation attaches an onFormSubmit trigger to a linked Google Sheet and calls a Slack incoming webhook from the trigger body. The pattern fails in production when the trigger silently stops firing after account re-authentication, the Slack webhook URL rotates, or a new question is added to the form and the field mapping breaks.
The underlying problem is structural. A script embedded in a Sheet has no deployment pipeline, no secrets management, and no delivery log. If the Slack post fails, there is no record of the failure and no retry.
No-code platforms solve the ownership problem but reintroduce cost. Because Zapier meters by task, a form receiving 500 responses per month consumes 500 tasks, which moves the integration into a higher billing tier with no change in complexity.
The pattern that avoids both failure modes is a declarative workflow: a YAML file committed to version control, with credentials stored separately from the workflow definition, and an explicit log of every delivery attempt.
The authentication constraint to handle first
Before fetching a response, a strict constraint must be resolved.
A Google Cloud service account can create and modify forms via the API, but it cannot read responses. Reading responses requires user-consent OAuth. The required scope is https://www.googleapis.com/auth/forms.responses.readonly.
This rules out service-account-only setups. The OAuth connection in the workflow must be authorised by a Google account that owns or has editor access to the form.
Step 1: Trigger on new responses
The Google Forms REST API, generally available since 2022, supports a watch resource that pushes response notifications into a Cloud Pub/Sub topic. That topic can drive a webhook trigger with sub-minute latency and no polling overhead.
For teams that prefer a simpler starting point, a scheduled poll every five minutes using GOOGLE_FORMS_GET_FORM covers most operational use cases without the Pub/Sub setup. The YAML example below uses this approach.
Whichever trigger method is selected, use the responseId returned by forms.responses.list as an idempotency key. Store processed IDs and skip any response already in the log. This prevents duplicate Slack posts if the trigger fires more than once for the same submission.

Step 2: Fetch the response and write the audit log
The forms.responses.list endpoint returns each submission as an object with three fields that matter for this workflow:
responseId: a stable identifier, useful as the idempotency key and as a reference in the audit logcreateTime: the UTC timestamp of submission, suitable for ordering and for display in the Slack messageanswers: a map keyed byquestionId, where each value contains atextAnswers.answersarray holding the submittedresponse values
Before posting to Slack, append a row to the Google Sheet linked to the form using GOOGLE_SHEETS_EDIT_SHEET. Write the responseId, createTime, and a delivery_status column set to PENDING_SLACK_POST. After the Slack post succeeds, a second GOOGLE_SHEETS_EDIT_SHEET action updates that status to POSTED_SUCCESSFULLY.
This turns the linked Sheet into an auditable delivery ledger. If a Slack post fails, the row status remains PENDING_SLACK_POST and the failure is visible without needing to inspect workflow logs separately.
Step 3: Format the Slack message with Block Kit
A plain-text "New form response" message provides the channel with no actionable information. Slack Block Kit solves this by enabling structured messages composed from a JSON blocks array. A single message supports up to 50 blocks, which is sufficient for any realistic form.
The useful block types for a Google Forms Slack notification are:
header: the message title, rendered in a larger weightdivider: a horizontal rule to separate sectionssectionwithfields: pairs of labelled values, useful forresponseIdandcreateTimesectionwithtextandmrkdwn: individual question-answer pairsactionswith abuttonelement: a link to the full submission using the form'sresponderUri
The responderUri is returned by the forms.get endpoint alongside the form structure. Include it as the url property on the button element so reviewers can open the original submission in one click.
Slack provides a free Block Kit Builder for previewing the JSON payload before embedding it in a workflow.
Use the SLACK_POST_BLOCKS_MESSAGE action to send the composed payload. Inbound events from Slack, such as a button click that should write back to the Sheet, arrive via INPUT_SLACK_EVENT, which the Slack Events API delivers wrapped in an event_callback envelope.
Putting it together: the complete workflow
The YAML below defines the full workflow as a Jinba Flow definition. Substitute your form ID, spreadsheet ID, question IDs, and connection names before deploying.
name: google-form-to-slack-alert
trigger:
type: SCHEDULE
cron: "*/5 * * * *" # Poll every 5 minutes
actions:
- name: get_latest_form_responses
type: GOOGLE_FORMS_GET_FORM
formId: "YOUR_FORM_ID"
google_oauth_connection: "your-google-connection"
- name: process_and_log_each_response
for_each: steps.get_latest_form_responses.responses
actions:
- name: log_to_sheet
type: GOOGLE_SHEETS_EDIT_SHEET
spreadsheetId: "YOUR_SPREADSHEET_ID"
range: "A1"
values:
- [
"{{ item.responseId }}",
"{{ item.createTime }}",
"PENDING_SLACK_POST"
]
google_oauth_connection: "your-google-connection"
- name: post_to_slack
type: SLACK_POST_BLOCKS_MESSAGE
channel: "#your-channel-name"
blocks: |
[
{
"type": "header",
"text": {
"type": "plain_text",
"text": "New Google Form Submission"
}
},
{ "type": "divider" },
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Submitted At:*\n{{ item.createTime }}" },
{ "type": "mrkdwn", "text": "*Response ID:*\n{{ item.responseId }}" }
]
},
{ "type": "divider" },
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Question 1:*\n> {{ item.answers['QUESTION_ID_1'].textAnswers.answers[0].value }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Question 2:*\n> {{ item.answers['QUESTION_ID_2'].textAnswers.answers[0].value }}"
}
},
{ "type": "divider" },
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View Full Response" },
"url": "YOUR_FORM_RESPONDER_URI/{{ item.responseId }}"
}
]
}
]
slack_connection: "your-slack-connection"
- name: update_log_status
type: GOOGLE_SHEETS_EDIT_SHEET
# Find the row by responseId and update delivery_status to POSTED_SUCCESSFULLY
spreadsheetId: "YOUR_SPREADSHEET_ID"
google_oauth_connection: "your-google-connection"
Two failure modes to plan for
Watch expiry. If the polling trigger is replaced with the Pub/Sub watch method, watches expire and must be actively renewed. An expired watch stops delivering notifications without any error surfacing in Slack or the Sheet. The delivery status log described above provides the signal: if the PENDING_SLACK_POST count in the Sheet grows without corresponding POSTED_SUCCESSFULLY entries, the watch has likely lapsed.
Form schema changes. When a question is added, removed, or reordered, the questionId keys in the answers map remain stable, but any hardcoded question label in the Block Kit payload can become incorrect. The safer pattern is to call forms.get at the start of each run, build the question-label map dynamically from the returned form structure, and use that map to render the Block Kit blocks. This adds one API call per run but makes the workflow resilient to form edits.
Extending to two-way communication
A common next requirement is writing back to the Sheet from Slack: for example, a reviewer clicks a button in the notification and marks the response as actioned, which updates the corresponding row.
This is handled by a second workflow triggered by INPUT_SLACK_EVENT. When a user interacts with the button in the Block Kit message, Slack sends an event payload to the workflow's endpoint, wrapped in a type: event_callback envelope. The workflow reads the responseId embedded in the button's value field and executes a GOOGLE_SHEETS_EDIT_SHEET action to update the target row.
This keeps both directions of the integration in versioned, auditable workflow definitions rather than dispersed across personal scripts.
What to do next
The workflow above eliminates the core visibility problem: form submissions arrive in a named Slack channel within five minutes, with the full response values formatted for immediate review, a direct link to the original submission via responderUri, and a delivery record written back to the source Sheet.
Define the OAuth connection using the https://www.googleapis.com/auth/forms.responses.readonly scope, replace the placeholder IDs in the YAML, and deploy. Schema-aware question rendering and the two-way Slack interaction pattern are incremental additions to the same declarative definition, not separate integrations to maintain.
Frequently Asked Questions
How do I send Google Forms responses to Slack automatically?
Google Forms responses can be sent to Slack automatically by using the Google Forms API to fetch new submissions and the Slack API to post a formatted message. The most reliable approach is a declarative workflow that triggers on a schedule (e.g., every five minutes) or via Pub/Sub notifications, fetches responses with forms.responses.list, and posts to Slack using Block Kit. This avoids the fragility of Apps Script and the per-task costs of Zapier. OAuth with the forms.responses.readonly scope is required, because service accounts cannot read responses.
Can a Google Cloud service account read Google Forms responses?
No, a Google Cloud service account cannot read Google Forms responses. The Forms API requires user-consent OAuth with the https://www.googleapis.com/auth/forms.responses.readonly scope to access response data. Service accounts can create and modify forms, but reading responses requires a Google account that owns or has editor access to the form to authorise the OAuth connection. This constraint must be handled before building the integration.
What is the best way to avoid duplicate Slack notifications for Google Forms?
The best way to avoid duplicate Slack notifications is to use the responseId from the Forms API as an idempotency key. Store processed response IDs in a log (e.g., a Google Sheet) and skip any response that already exists in the log before posting to Slack. This prevents duplicates if the trigger fires more than once for the same submission, which can happen with polling or Pub/Sub retries. Implementing this check is a simple but essential step for production reliability.
How do I format Google Forms responses in Slack using Block Kit?
Use Slack's Block Kit to compose structured messages with a JSON blocks array. Include a header block for the title, divider blocks to separate sections, section blocks with fields for metadata such as responseId and createTime, and section blocks with text and mrkdwn for each question-answer pair. Finally, add an actions block with a button linking to the full submission via the form's responderUri. This makes notifications actionable and easy to scan. The payload can be previewed using the free Block Kit Builder.
What are common failure modes in a Google Forms to Slack integration?
Common failure modes include silent trigger failures (e.g., Apps Script triggers stopping after re-authentication), expired Pub/Sub watches that stop delivering notifications without errors, and broken field mappings when form questions change. Hardcoded question labels in Slack messages can also become incorrect after schema edits. To mitigate, maintain a delivery status log, renew watches proactively, and dynamically build question-label maps from forms.get at runtime rather than hardcoding them.
How do I handle form schema changes in my Google Forms to Slack workflow?
Handle form schema changes by calling forms.get at the start of each workflow run and building a dynamic map of questionId to question labels. Use this map to render the Block Kit blocks instead of hardcoding question labels. While questionId keys remain stable when questions are added, removed, or reordered, hardcoded labels can break. This approach adds one API call per run but makes the workflow resilient to form edits, avoiding silent failures or misleading Slack messages.
Do I need Zapier to connect Google Forms to Slack?
Zapier is not required to connect Google Forms to Slack. While Zapier offers a no-code solution, its paid plans start at $19.99 per month and meter by task, so costs scale with response volume. A more cost-effective, auditable, and version-controlled integration can be built using the Google Forms API and Slack API directly, or with a declarative workflow tool such as Jinba Flow. This approach provides full control over authentication, logging, and error handling without per-task fees.
How do I enable two-way communication between Slack and Google Forms?
Enable two-way communication by setting up a second workflow triggered by Slack events (INPUT_SLACK_EVENT). When a user clicks a button in a Slack notification, Slack sends an event_callback payload to the workflow endpoint. The workflow can extract the responseId from the button's value field and use GOOGLE_SHEETS_EDIT_SHEET to update the corresponding row in the linked Google Sheet. This keeps both directions of the integration in versioned, auditable workflow definitions, enabling actions such as marking a response as reviewed directly from Slack.
