Set up and receive push notifications

You can use the methods in the Watches collection to receive notifications when data changes in forms. This page provides a conceptual overview and instructions for setting up and receiving push notifications.

Overview

The Google Forms API push notifications feature allows applications to subscribe to notifications when data changes in forms. Notifications are delivered to a Cloud Pub/Sub topic, usually within minutes of the change.

To receive push notifications, you need to set up a Cloud Pub/Sub topic and provide that topic's name when you create a watch for the appropriate event type.

Below are definitions of key concepts used in this documentation:

  • A target is a place where notifications are sent. The only supported target is a Cloud Pub/Sub topic.
  • An event type is a category of notifications that a third party application can subscribe to.
  • A watch is an instruction to the Forms API to deliver notifications for a particular event type on a particular form to a target.

Once you create a watch for an event type on a particular form, that watch's target (which is a Cloud Pub/Sub topic) receives notifications from those events on that form until the watch expires. Your watch lasts a week, but you can extend it at any point before it expires by making a request to watches.renew().

Your Cloud Pub/Sub topic only receives notifications about forms that you can view with the credentials you supply. For example, if the user revokes permission from your application or loses edit access to a watched form, notifications are no longer delivered.

Available event types

The Google Forms API currently offers two categories of events:

  • EventType.SCHEMA, which notifies about edits to a form's content and settings.
  • EventType.RESPONSES, which notifies when form responses (both new and updated) are submitted.

Notification responses

Notifications are encoded with JSON and contain:

  • The ID of the triggering form
  • The ID of the triggering watch
  • The type of event that triggered the notification
  • Other fields set by Cloud Pub/Sub, such as messageId and publishTime

Notifications do not contain detailed form or response data. After each notification is received, a separate API call is required to fetch fresh data. See Suggested usage for how to accomplish this.

The following snippet demonstrates a sample notification for a schema change:

{
  "attributes": {
    "eventType": "SCHEMA",
    "formId": "18Xgmr4XQb-l0ypfCNGQoHAw2o82foMr8J0HPHdagS6g",
    "watchId": "892515d1-a902-444f-a2fe-42b718fe8159"
  },
  "messageId": "767437830649",
  "publishTime": "2021-03-31T01:34:08.053Z"
}

The following snippet demonstrates a sample notification for a new response:

{
  "attributes": {
    "eventType": "RESPONSES",
    "formId": "18Xgmr4XQb-l0ypfCNGQoHAw2o82foMr8J0HPHdagS6g",
    "watchId": "5d7e5690-b1ff-41ce-8afb-b469912efd7d"
  },
  "messageId": "767467004397",
  "publishTime": "2021-03-31T01:43:57.285Z"
}

Set up a Cloud Pub/Sub topic

Notifications are delivered to Cloud Pub/Sub topics. From Cloud Pub/Sub, you can receive notifications on a web hook, or by polling a subscription endpoint.

To set up a Cloud Pub/Sub topic, do the following:

  1. Complete the Cloud Pub/Sub Prerequisites.
  2. Set up a Cloud Pub/Sub client.
  3. Review the Cloud Pub/Sub pricing, and enable billing for your Developer Console project.
  4. Create a Cloud Pub/Sub topic one of three ways:

  5. Create a Subscription in Cloud Pub/Sub to tell Cloud Pub/Sub how to deliver your notifications.

  6. Finally, before creating watches that target your topic, you need to grant permission to the Forms notifications service account (forms-notifications@system.gserviceaccount.com) to publish to your topic.

Create a watch

Once you have a topic that the Forms API push notifications service account can publish to, you can create notifications using the watches.create() method. This method validates that the provided Cloud Pub/Sub topic can be reached by the push notifications service account, and fails if it cannot reach the topic; for example, if the topic does not exist or you have not granted it publish permission on that topic.

Python

forms/snippets/create_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secret.json", SCOPES)
  creds = tools.run_flow(flow, store)

service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

watch = {
    "watch": {
        "target": {"topic": {"topicName": "<YOUR_TOPIC_PATH>"}},
        "eventType": "RESPONSES",
    }
}

form_id = "<YOUR_FORM_ID>"

# Print JSON response after form watch creation
result = service.forms().watches().create(formId=form_id, body=watch).execute()
print(result)

Node.js

forms/snippets/create_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const watchRequest = {
    watch: {
      target: {
        topic: {
          topicName: 'projects/<YOUR_TOPIC_PATH>',
        },
      },
      eventType: 'RESPONSES',
    },
  };
  const res = await forms.forms.watches.create({
    formId: formID,
    requestBody: watchRequest,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

Delete a watch

Python

forms/snippets/delete_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secret.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"
watch_id = "<YOUR_WATCH_ID>"

# Print JSON response after deleting a form watch
result = (
    service.forms().watches().delete(formId=form_id, watchId=watch_id).execute()
)
print(result)

Node.js

forms/snippets/delete_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';
const watchID = '<YOUR_FORMS_WATCH_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const res = await forms.forms.watches.delete({
    formId: formID,
    watchId: watchID,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

Authorization

Like all calls to the Forms API, calls to watches.create() must be authorized with an authorization token. The token must include a scope that grants read access to the data about which notifications are being sent.

For notifications to be delivered, the application must retain an OAuth grant from the authorized user with the required scopes. If the user disconnects the application, notifications cease and the watch may be suspended with an error. To resume notifications after regaining authorization, see Renew a watch.

List a form's watches

Python

forms/snippets/list_watches.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secrets.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"

# Print JSON list of form watches
result = service.forms().watches().list(formId=form_id).execute()
print(result)

Node.js

forms/snippets/list_watches.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';

async function runSample(query) {
  const auth = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/forms.responses.readonly',
  });
  const forms = google.forms({
    version: 'v1',
    auth: auth,
  });
  const res = await forms.forms.watches.list({formId: formID});
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

Renew a watch

Python

forms/snippets/renew_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secrets.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"
watch_id = "<YOUR_WATCH_ID>"

# Print JSON response after renewing a form watch
result = (
    service.forms().watches().renew(formId=form_id, watchId=watch_id).execute()
)
print(result)

Node.js

forms/snippets/renew_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';
const watchID = '<YOUR_FORMS_WATCH_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const res = await forms.forms.watches.renew({
    formId: formID,
    watchId: watchID,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

Throttling

Notifications are throttled—each watch can receive at most one notification every thirty seconds. This threshold of frequency is subject to change.

Because of throttling, a single notification may correspond to multiple events. In other words, a notification indicates that one or more events have occurred since the last notification.

Limits

At any time, for a given form and event type, each Cloud Console project can have:

  • up to 20 watches total
  • up to one watch per end user

Additionally, at any time, each form is limited to 50 watches per event type in total across all Cloud Console projects.

A watch is associated with an end user when it is created or renewed with credentials for that user. A watch is suspended if the associated end user loses access to the form or revokes the app's access to the form.

Reliability

Each watch is notified at least once after each event in all but extraordinary circumstances. In the vast majority of cases, a notification is delivered within minutes of an event.

Errors

If notifications for a watch persistently fail to be delivered, the watch state becomes SUSPENDED and the watch's errorType field is set. To reset a suspended watch's state to ACTIVE and resume notifications, see Renew a watch.

Suggested usage

  • Use a single Cloud Pub/Sub topic as the target of many watches.
  • When receiving a notification on a topic, the form ID is included in the notification payload. Use it with the event type to know what data to fetch and which form to fetch it from.
  • To fetch updated data after a notification with EventType.RESPONSES, call forms.responses.list().
    • Set the filter on the request to timestamp > timestamp_of_the_last_response_you_fetched.
  • To fetch updated data after a notification with EventType.SCHEMA, call forms.get().