Configurar e receber notificações push

Você pode usar os métodos na coleção Watches para receber notificações quando os dados mudarem nos formulários. Nesta página, você encontra uma visão geral conceitual e instruções para configurar e receber notificações push.

Visão geral

Com o recurso de notificações push da API Google Forms, os aplicativos podem se inscrever para receber notificações quando os dados mudam nos formulários. As notificações são entregues em 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 uma observação para o tipo de evento adequado.

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 aceito é 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 monitoramento é uma instrução para a API Forms enviar notificações de um determinado tipo de evento em um formulário específico para um destino.

Depois de criar uma observação para um tipo de evento em um formulário específico, o destino dessa observação (que é um tópico do Cloud Pub/Sub) recebe notificações desses eventos no formulário até que a observação expire. O período de observação dura uma semana, mas você pode estender a qualquer momento antes do vencimento fazendo uma solicitação para watches.renew().

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

Tipos de evento disponíveis

No momento, 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 da observação 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 de formulários ou respostas. 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 um exemplo de notificação 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 um exemplo de notificação 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, você pode receber notificações em um webhook ou sondando 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 Developer Console.
  4. Crie um tópico do Cloud Pub/Sub de uma destas três maneiras:

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

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

Criar um alerta

Depois de ter um tema em que a conta de serviço de notificações push da API Forms pode publicar, crie notificações usando o método watches.create(). Esse método valida se a conta de serviço de notificações push pode acessar o tópico do Cloud Pub/Sub fornecido 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 nele.

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 uma observação

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 concessão do OAuth do usuário autorizado com os escopos necessários. Se o usuário desconectar o aplicativo, as notificações vão parar, e o relógio poderá ser suspenso com um erro. Para retomar as notificações depois de 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 observações por tipo de evento no total em todos os projetos do console do Cloud.

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

Confiabilidade

Cada observação é notificada 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 não forem entregues de forma consistente, o estado do relógio vai 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 várias observações.
  • Ao receber uma notificação sobre um tópico, o ID do formulário é incluído no payload da notificação. Use 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().