Configurar e receber notificações push

É possível usar os métodos na coleção Watches para receber notificações quando os dados mudam nos formulários. Esta página oferece uma visão geral conceitual e instruções para configurar e receber notificações push.

Visão geral

O recurso de notificações push da API Google Forms permite que os aplicativos se inscrevam para receber notificações quando os dados mudam nos formulários. As notificações são entregues a um tópico do Cloud Pub/Sub, geralmente em minutos após a mudança.

Para receber notificações push, configure um tópico do Cloud Pub/Sub e forneça o nome dele ao criar um relógio para o tipo de evento apropriado.

Confira abaixo as definições dos principais conceitos usados nesta documentação:

  • Um destino é um lugar para onde as notificações são enviadas. O único destino com suporte é um tópico do Cloud Pub/Sub.
  • Um tipo de evento é uma categoria de notificações a que um aplicativo de terceiros pode se inscrever.
  • Um watch é uma instrução para a API Forms entregar notificações de um tipo de evento específico em um formulário específico para um destino.

Depois de criar um relógio para um tipo de evento em um formulário específico, o destino desse relógio (que é um tópico do Cloud Pub/Sub) recebe notificações desses eventos no formulário até que o relógio expire. O relógio dura uma semana, mas é possível estendê-lo a qualquer momento antes da expiração fazendo uma solicitação para watches.renew().

O tópico do Cloud Pub/Sub só recebe notificações sobre formulários que podem ser visualizados com as credenciais fornecidas. Por exemplo, se o usuário revogar a permissão do aplicativo ou perder o acesso de edição a um formulário monitorado, as notificações não serão mais entregues.

Tipos de evento disponíveis

A API Google Forms oferece duas categorias de eventos:

  • EventType.SCHEMA, que notifica sobre edições no conteúdo e nas configurações de um formulário.
  • EventType.RESPONSES, que notifica quando as respostas do formulário (novas e atualizadas) são enviadas.

Respostas de notificação

As notificações são codificadas com JSON e contêm:

  • O ID do formulário de acionamento
  • O ID do relógio de acionamento
  • O tipo de evento que acionou a notificação
  • Outros campos definidos pelo Cloud Pub/Sub, como messageId e publishTime

As notificações não contêm dados detalhados do formulário ou da resposta. Depois que cada notificação é recebida, uma chamada de API separada é necessária para buscar dados atualizados. Consulte Uso sugerido para saber como fazer isso.

O snippet a seguir demonstra uma notificação de amostra para uma mudança de esquema:

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

O snippet a seguir demonstra uma notificação de amostra para uma nova resposta:

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

Configurar um tópico do Cloud Pub/Sub

As notificações são entregues aos tópicos do Cloud Pub/Sub. No Cloud Pub/Sub, é possível receber notificações em um webhook ou pesquisando um endpoint de assinatura.

Para configurar um tópico do Cloud Pub/Sub, faça o seguinte:

  1. Conclua os pré-requisitos do Cloud Pub/Sub.
  2. Configure um cliente do Cloud Pub/Sub.
  3. Analise os preços do Cloud Pub/Sub, e ative o faturamento para seu projeto do console do Google Cloud.
  4. Crie um tópico do Cloud Pub/Sub de uma das três maneiras:

  5. Crie uma assinatura no Cloud Pub/Sub para informar ao Cloud Pub/Sub como entregar as notificações.

  6. Por fim, antes de criar relógios que segmentam seu tópico, é necessário conceder permissão à conta de serviço de notificações do Forms (forms-notifications@system.gserviceaccount.com) para publicar no tópico.

Criar um relógio

Depois de ter um tópico em que a conta de serviço de notificações push da API Forms pode publicar, é possível criar notificações usando o watches.create(). Esse método valida se o tópico do Cloud Pub/Sub fornecido pode ser acessado pela conta de serviço de notificações push e falha se não for possível acessar o tópico. Por exemplo, se o tópico não existir ou se você não tiver concedido permissão de publicação a ele.

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
import path from 'node:path';
import {authenticate} from '@google-cloud/local-auth';
import {forms} from '@googleapis/forms';

// TODO: Replace with a valid form ID.
const formID = '<YOUR_FORM_ID>';

/**
 * Creates a watch on a form to get notifications for new responses.
 */
async function createWatch() {
  // Authenticate with Google and get an authorized client.
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });

  // Create a new Forms API client.
  const formsClient = forms({
    version: 'v1',
    auth: authClient,
  });

  // The request body to create a watch.
  const watchRequest = {
    watch: {
      target: {
        topic: {
          // TODO: Replace with a valid Cloud Pub/Sub topic name.
          topicName: 'projects/<YOUR_TOPIC_PATH>',
        },
      },
      // The event type to watch for. 'RESPONSES' is the only supported type.
      eventType: 'RESPONSES',
    },
  };

  // Send the request to create the watch.
  const result = await formsClient.forms.watches.create({
    formId: formID,
    requestBody: watchRequest,
  });

  console.log(result.data);
  return result.data;
}

Excluir um relógio

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
import path from 'node:path';
import {authenticate} from '@google-cloud/local-auth';
import {forms} from '@googleapis/forms';

// TODO: Replace with a valid form ID.
const formID = '<YOUR_FORM_ID>';
// TODO: Replace with a valid watch ID.
const watchID = '<YOUR_FORMS_WATCH_ID>';

/**
 * Deletes a watch from a form.
 */
async function deleteWatch() {
  // Authenticate with Google and get an authorized client.
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });

  // Create a new Forms API client.
  const formsClient = forms({
    version: 'v1',
    auth: authClient,
  });

  // Send the request to delete the watch.
  const result = await formsClient.forms.watches.delete({
    formId: formID,
    watchId: watchID,
  });

  console.log(result.data);
  return result.data;
}

Autorização

Como todas as chamadas para a API Forms, as chamadas para watches.create() precisam ser autorizadas com um token de autorização. O token precisa incluir um escopo que conceda acesso de leitura aos dados sobre os quais as notificações estão sendo enviadas.

Para que as notificações sejam entregues, o aplicativo precisa manter uma permissão de acesso OAuth do usuário autorizado com os escopos necessários. Se o usuário desconectar o aplicativo, as notificações serão interrompidas e o relógio poderá ser suspenso com um erro. Para retomar as notificações após recuperar a autorização, consulte Renovar um relógio.

Listar os relógios de um formulário

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
import path from 'node:path';
import {authenticate} from '@google-cloud/local-auth';
import {forms} from '@googleapis/forms';

// TODO: Replace with a valid form ID.
const formID = '<YOUR_FORM_ID>';

/**
 * Lists the watches for a given form.
 */
async function listWatches() {
  // Authenticate with Google and get an authorized client.
  const auth = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/forms.responses.readonly',
  });

  // Create a new Forms API client.
  const formsClient = forms({
    version: 'v1',
    auth,
  });

  // Get the list of watches for the form.
  const result = await formsClient.forms.watches.list({
    formId: formID,
  });

  console.log(result.data);
  return result.data;
}

Renovar um relógio

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
import path from 'node:path';
import {authenticate} from '@google-cloud/local-auth';
import {forms} from '@googleapis/forms';

// TODO: Replace with a valid form ID.
const formID = '<YOUR_FORM_ID>';
// TODO: Replace with a valid watch ID.
const watchID = '<YOUR_FORMS_WATCH_ID>';

/**
 * Renews a watch on a form.
 */
async function renewWatch() {
  // Authenticate with Google and get an authorized client.
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });

  // Create a new Forms API client.
  const formsClient = forms({
    version: 'v1',
    auth: authClient,
  });

  // Send the request to renew the watch.
  const result = await formsClient.forms.watches.renew({
    formId: formID,
    watchId: watchID,
  });

  console.log(result.data);
  return result.data;
}

Limitação

As notificações são limitadas. Cada relógio pode receber no máximo uma notificação a cada 30 segundos. Esse limite de frequência está sujeito a mudanças.

Devido à limitação, uma única notificação pode corresponder a vários eventos. Em outras palavras, uma notificação indica que um ou mais eventos ocorreram desde a última notificação.

Limites

A qualquer momento, para um determinado formulário e tipo de evento, cada projeto do console do Cloud pode ter:

  • até 20 relógios no total
  • até um relógio por usuário final

Além disso, a qualquer momento, cada formulário é limitado a 50 relógios por tipo de evento no total em todos os projetos do console do Cloud.

Um relógio é associado a um usuário final quando é criado ou renovado com credenciais para esse usuário. Um relógio é suspenso se o usuário final associado perder o acesso ao formulário ou revogar o acesso do app ao formulário.

Confiabilidade

Cada relógio é notificado pelo menos uma vez após cada evento, exceto em circunstâncias extraordinárias. Na grande maioria dos casos, uma notificação é entregue em minutos após um evento.

Erros

Se as notificações de um relógio falharem persistentemente, o estado do relógio se tornará SUSPENDED e o campo errorType do relógio será definido. Para redefinir o estado de um relógio suspenso para ACTIVE e retomar as notificações, consulte Renovar um relógio.

Uso sugerido

  • Use um único tópico do Cloud Pub/Sub como destino de muitos relógios.
  • Ao receber uma notificação em um tópico, o ID do formulário é incluído no payload da notificação. Use-o com o tipo de evento para saber quais dados buscar e de qual formulário.
  • Para buscar dados atualizados após uma notificação com EventType.RESPONSES, chame forms.responses.list().
    • Defina o filtro na solicitação como timestamp > timestamp_of_the_last_response_you_fetched.
  • Para buscar dados atualizados após uma notificação com EventType.SCHEMA, chame forms.get().