Voltar para Notícias para desenvolvedores

Como reservar uma hora marcada com o WhatsApp Flows: criando um backend Node.js

27 de fevereiro de 2024PorGafi G & Iryna Wagner

Com o WhatsApp Flows, você pode criar mensagens interativas para usuários a fim de concluir ações diretamente no WhatsApp. Os fluxos permitem que você crie telas para a interação com o usuário. Por exemplo, você pode criar formulários de entrada simples para coletar cadastros ou enviar análises. Além disso, você pode projetar fluxos complexos em várias telas para reservar uma hora marcada.

Este guia ajudará você a criar um app Node.js que permite que os usuários reservem uma hora marcada por meio do WhatsApp Flows. Você criará um fluxo na Plataforma do WhatsApp Business, depois configurará um webhook para receber a resposta do fluxo e reservar uma hora marcada.

Pré-requisitos

Para acompanhar o tutorial, você deve ter:

Como criar um fluxo do WhatsApp

Há duas formas de criar um fluxo do WhatsApp: usando o Configurador de fluxos, que pode ser acessado via Gerenciador do WhatsApp, e a API de Fluxos. Este tutorial mostrará como usar o Configurador de fluxos.

Como criar um fluxo

No menu esquerdo do painel do Gerenciador do WhatsApp, selecione Ferramentas da conta. Depois, clique em Fluxos.

Gráfico do Gerenciador do WhatsApp

Clique em Criar fluxo no canto superior direito.

gráfico de como criar fluxo

Na caixa de diálogo exibida, preencha os detalhes para o fluxo de reserva de hora marcada:

  • Nome — Digite BookAppointment (Reserva de hora marcada), ou escolha outro nome se desejar.
  • Categorias — Selecione Reserva de hora marcada.
  • Modelo — Escolha Reservar uma hora marcada. Você usará o modelo porque ele contém os elementos necessários para reservar uma hora marcada. Esses elementos incluem telas para os detalhes da hora marcada, entrada para detalhes do usuário, resumo da hora marcada e exibição dos termos da empresa. Você pode personalizar ainda mais o modelo para que ele corresponda ao seu caso de uso.
 Gráfico de reserva de horas marcadas

Clique em Enviar para criar o fluxo.

Você pode ver uma prévia do fluxo à direita do Configurador de UI. A tela de reserva de horas marcadas permite que o usuário escolha os detalhes do agendamento, como a localização e a data. A tela de detalhes é onde o usuário vai inserir suas informações. A tela de resumo mostra o resumo da reserva de horas marcadas. A última tela mostra os termos da empresa.

O fluxo permanece no estado de rascunho durante a edição. No momento, você pode compartilhá-lo com sua equipe apenas para fins de teste. Para compartilhá-lo com um público amplo, você precisará publicá-lo. No entanto, não é possível editar o fluxo depois de publicado. Como você ainda precisará adicionar o URL do ponto de extremidade para esse fluxo de reserva de horas marcadas, deixe-o como um rascunho por enquanto e avance para a próxima etapa, onde você configurará o ponto de extremidade.

Como configurar o ponto de extremidade do fluxo

O WhatsApp Flows permite que você se conecte a um ponto de extremidade externo. Esse ponto de extremidade pode fornecer dados dinâmicos para seu fluxo e controlar o roteamento. Ele também recebe do fluxo respostas enviadas pelo usuário.

Para fins de teste, este artigo usa o Glitch para hospedar o ponto de extremidade. Usar o Glitch é opcional, e não é necessário para usar os fluxos. Você pode clonar o código do ponto de extremidade do GitHub e executá-lo em qualquer ambiente da sua preferência.

Acesse o código do ponto de extremidade no Glitch e faça um remix para obter seu domínio único. Para fazer o remix, clique em Remixar na parte superior da página. Um domínio único aparecerá como espaço reservado no elemento de entrada à direita da página do Glitch.

Antes de continuar, vamos falar sobre o código. Há quatro arquivos JavaScript no diretório src: encryption.js, flow.js, keyGenerator.js, e server.js. O arquivo de entrada é server.js. Vamos analisá-lo primeiro.

server.js

O arquivo server.js começa configurando o app Express para usar o middleware express.json para analisar solicitações JSON recebidas. Depois, ele carrega as variáveis do ambiente necessárias para o ponto de extremidade.

const { APP_SECRET, PRIVATE_KEY, PASSPHRASE, PORT = "3000" } = process.env;

APP_SECRET é usado na verificação da assinatura. Ele ajuda você a verificar se uma mensagem está sendo recebida via WhatsApp e se, portanto, é seguro processá-la. Você deverá adicioná-lo ao arquivo .env.

Para acessar o APP_SECRET, navegue até o painel do app no Meta for Developers. No painel de navegação esquerdo, em Configurações do app, escolha Básico. Clique em Exibir em Chave secreta do app e copie a chave secreta. Depois, retorne para o Glitch, abra o arquivo .env e crie uma variável com o nome de APP_SECRET com o valor da chave secreta que você copiou.

PRIVATE_KEY ajuda a descriptografar as mensagens recebidas. O PASSPHRASE será usado para verificar a chave privada. Juntamente com a chave privada, também será necessário ter a chave pública correspondente, que você atualizará depois. Nunca use as chaves privadas para suas contas de produção aqui. Crie uma lista privada temporária para testar no Glitch. Depois, substitua-a pela sua chave de produção na sua própria infraestrutura.

Gere o par da chave pública-privada executando o comando abaixo no terminal do Glitch. Substitua <your-passphrase> pela sua senha designada. Acesse o terminar do Glitch clicando na aba TERMINAL na parte inferior da página.

node src/keyGenerator.js <your-passphrase>

Copie a senha e a chave privada e cole-as no arquivo .env. Clique no arquivo intitulado .env na barra lateral esquerda. Depois, clique em ✏️ Texto sem formatação na parte superior. Não faça a edição diretamente na UI, pois isso quebrará a formatação da chave.

Depois de definir as variáveis do ambiente, copie a chave pública que você gerou e carregue a chave pública via Graph API.

O arquivo server.js também contém um ponto de extremidade POST que realiza diferentes etapas:

  • Verifica se a chave privada está presente:
       if (!PRIVATE_KEY) { throw new Error('Private key is empty. Please check your env variable "PRIVATE_KEY".'); }
  • Valida a assinatura da solicitação usando a função isRequestSignatureValid encontrada na parte inferior do arquivo:
if(!isRequestSignatureValid(req)) { // Retorna código de status 432 se a assinatura de solicitação não corresponder. // Para saber mais sobre os códigos de erro retornados, acesse: https://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codes return res.status(432).send(); }
  • Descriptografa mensagens recebidas usando a função decryptRequest encontrada no arquivo encryption.js:
      let decryptedRequest = null; try { decryptedRequest = decryptRequest(req.body, PRIVATE_KEY, PASSPHRASE); } catch (err) { console.error(err); if (err instanceof FlowEndpointException) { return res.status(err.statusCode).send(); } return res.status(500).send(); } const { aesKeyBuffer, initialVectorBuffer, decryptedBody } = decryptedRequest; console.log("💬 Decrypted Request:", decryptedBody);
  • Decide qual tela do fluxo exibir para o usuário. Você analisará a função getNextScreen em detalhes posteriormente.

const screenResponse = await getNextScreen(decryptedBody);

       console.log("👉 Response to Encrypt:", screenResponse);
  • Criptografa a resposta a ser enviada para o usuário:
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));

encryption.js

O arquivo contém a lógica para criptografar e descriptografar mensagens trocadas para fins de segurança. Este tutorial não se concentrará no funcionamento do arquivo.

keyGenerator.js

Este arquivo ajuda a gerar as chaves públicas e privadas, como você viu antes. Assim como acontece com o arquivo encryption.js, este tutorial não vai explorar o arquivo keyGenerator.js em detalhes.

flow.js

A lógica para lidar com o fluxo está armazenada neste arquivo. Começa com um objeto atribuído ao nome SCREEN_RESPONSES. O objeto contém identificações da tela com os detalhes correspondentes, como nos dados predefinidos usados nas trocas de dados. Esse objeto é gerado no Configurador de fluxo em "..." > Ponto de extremidade > Trechos de código > Respostas. No mesmo objeto, você também terá uma outra identificação, SUCCESS, que é enviada de volta para o dispositivo do cliente quando o fluxo é concluído com sucesso. Isso encerra o fluxo.

A função getNextScreen contém a lógica que orienta o ponto de extremidade sobre quais dados de fluxo exibir para o usuário. Ela começa extraindo os dados necessários da mensagem descriptografada.

const { screen, data, version, action, flow_token } = decryptedBody;

Os pontos de extremidade do WhatsApp Flows recebem três solicitações:

Você pode encontrar os detalhes na documentação do ponto de extremidade.

A função trata da verificação de integridade e notificações de erro usando declarações if e responde de acordo, como mostrado no trecho abaixo:

// handle health check request if (action === "ping") { return { version, data: { status: "active", }, }; } // handle error notification if (data?.error) { console.warn("Received client error:", data); return { version, data: { acknowledged: true, }, }; }
        

Quando um usuário clica no botão de chamada para ação do fluxo, uma ação INIT é acionada. Essa ação retorna a tela da reserva de horas marcadas juntamente com os dados. Ela também desabilita a localização, a data e os menus suspensos de horário para garantir que o usuário preencha todos os campos.

Por exemplo, o menu suspenso de datas é habilitado apenas quando o menu suspenso de localização é preenchido. A desabilitação e habilitação dos campos são tratadas quando uma solicitação data_exchange é recebida.

// lidar com solicitação inicial ao abrir o fluxo e exibir tela HORA MARCADA se (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // esses campos estão desabilitados inicialmente. Cada campo é habilitado quando os campos anteriores são selecionados is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }

Para ações data_exchange, é usada uma estrutura switch case para determinar quais dados enviar de volta com base na identificação da tela. Se a identificação da tela for APPOINTMENT, os campos do menu suspenso são habilitados somente quando os menus suspensos anteriores estiverem selecionados.

// Cada campo é habilitado apenas quando os campos anteriores são selecionados is_location_enabled: Boolean(data.department), is_date_enabled: Boolean(data.department) && Boolean(data.location), is_time_enabled: Boolean(data.department) && Boolean(data.location) && Boolean(data.date)

Para a tela de DETALHES, o título das propriedades de objeto de dados, como localização e departamento, são extraídos do objeto SCREEN_RESPONSES.APPOINTMENT.data. Esse código supõe que há uma correspondência válida. Por isso, ele pode apresentar um erro se nenhum objeto correspondente for encontrado.

Agora, pegue uma instância do objeto de localização. A seleção do objeto de localização específico é determinada pela correspondência da propriedade id dos objetos na matriz com o valor de data.location.

const departmentName = SCREEN_RESPONSES.APPOINTMENT.data.department.find( (dept) => dept.id === data.department ).title; const locationName = SCREEN_RESPONSES.APPOINTMENT.data.location.find( (loc) => loc.id === data.location ).title; const dateName = SCREEN_RESPONSES.APPOINTMENT.data.date.find( (date) => date.id === data.date

).title;

Os valores são concatenados e retornados em resposta para renderizar a tela RESUMO.

const appointment = `${departmentName} at ${locationName} ${dateName} at ${data.time}`; const details = `Name: ${data.name} Email: ${data.email} Phone: ${data.phone} "${data.more_details}"`; return { ...SCREEN_RESPONSES.SUMMARY, data: { appointment, details, // return the same fields sent from client back to submit in the next step ...data, }, };
        

Depois que a tela RESUMO é enviada do cliente, uma resposta de sucesso é enviada para o dispositivo do cliente para marcar o fluxo como concluído. O flow_token é um identificador único que você pode definir ao enviar o fluxo para o usuário.

// enviar resposta de sucesso para concluir e encerrar o fluxo retorna { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };

A tela TERMS não tem dados para serem trocados. Por isso, ela não é tratada pelo ponto de extremidade.

Como adicionar o ponto de extremidade ao fluxo

Na parte superior da página do Glitch, você pode copiar o URL clicando no ícone de menu de três linhas e selecionando Copiar link. Você também pode receber o link clicando em Compartilhar no canto superior direito.

Acesse o Editor de fluxo. Clique em Configurar no banner marrom que aparece na parte superior do editor.

Uma janela pop-up será exibida, permitindo que você configure o URL do ponto de extremidade, número de telefone da empresa e app no Meta for Developers. Depois de fazer as configurações necessárias, realize uma verificação de integridade. Primeiro, execute a prévia interativa e selecione Solicitar dados em Solicitar dados na primeira tela nas configurações da prévia interativa. Isso envia uma solicitação para o ponto de extremidade para recuperar os dados da primeira tela, verificando se o ponto de extremidade está disponível e se você implementou uma verificação de integridade.

Depois, publique o fluxo clicando no menu de três pontos (...) e selecionando Publicar. Isso enviará uma solicitação de verificação de integridade para seu ponto de extremidade com action === "ping" para verificar se o ponto de extremidade está configurado antes de publicar.

Gráfico do ponto de extremidade

Como testar o fluxo

Depois de concluir as configurações, alterne para a prévia novamente no WhatsApp Builder Ui para testar o fluxo. No pop-up exibido, selecione o número de telefone e escolha a opção Solicitar dados em Solicitar dados na primeira tela. Feche o fluxo clicando no ícone X para iniciar o teste do fluxo novamente no botão de CTA.

Abra o registro do Glitch clicando na aba REGISTROS. Limpe-a clicando em Limpar. Depois, retorne à prévia do WhatsApp Builder UI. Clique em Ver uma prévia do fluxo. Você verá algo como o seguinte:

Gráfico de ver prévia do fluxo

Agora, retorne para os registros do Glitch. Você verá uma ação INIT, o token do fluxo e outros detalhes sob a solicitação descriptografadas. Há também uma resposta para criptografar enviada de volta ao fluxo do usuário assim que o menu suspenso do departamento for selecionado.

gráfico de solicitação descriptografado

Continue para selecionar o departamento. Observe como is_location_enabled está configurado como true e a ação mudou para data_exchange.

gráfico do data_exchange

Continue testando o fluxo e observe as mudanças de dados nos registros do Glitch. Registros semelhantes serão gerados quando os usuários interagirem com o fluxo nos dispositivos móveis.

Na próxima seção, você criará um webhook que envia uma mensagem de confirmação para o usuário quando ele reserva uma hora marcada.

Como configurar o webhook

Quando um usuário conclui o fluxo, uma mensagem marcando a conclusão do fluxo é enviada para o webhook assinado. Nesse webhook, você notificará o usuário sobre a reserva da hora marcada bem-sucedida com uma mensagem no bate-papo. Semelhante ao ponto de extremidade, você também usará o Glitch para testes. Você pode acessar o código e remixá-lo aqui.

Usar o Glitch é opcional, e não é necessário para usar os fluxos. Você pode clonar o código do webhook do GitHub e executá-lo em qualquer ambiente da sua preferência.

Como definir variáveis de ambiente

Para definir as variáveis do ambiente, abra o arquivo .env no Glitch. Defina VERIFY_TOKEN para qualquer string que você preferir, FLOW_ID com o ID do fluxo e GRAPH_API_TOKEN para o token de acesso da sua conta do WhatsApp Business. Você pode obter o token de acesso do menu do seu app no Meta for Developers quando clicar em Configuração da API na seção WhatsApp no painel de navegação à esquerda.

Gráfico de configuração da API

Na página que é renderizada, clique no botão Copiar sob o cartão Token de acesso temporário. Copie sua chave no arquivo .env.

Como assinar o webhook no painel da Meta

Na conta do Meta for Developers, clique no menu Configuração sob WhatsApp no painel de navegação esquerdo.

gráfico de configuração

No cartão Webhook, clique em Editar. Na caixa de diálogo aberta, cole o URL do Glitch copiado e acrescente o /webhook a ele no campo URL de retorno de chamada. No campo Verificar token, adicione o token da variável VERIFY_TOKEN do arquivo .env. Quando tiver concluído, clique em Verificar e salvar. A caixa de diálogo será encerrada e você retornará para a tela principal. Clique em Gerenciar e consulte o campo Mensagens. O webhook está pronto.

Tutorial de código do webhook

O código contém duas rotas: POST /webhook e GET /webhook. O código GET trata das solicitações de verificação do webhook verificando o token fornecido em relação a um token de verificação predefinido e respondendo com os códigos de status apropriados e um token de desafio.

const verify_token = process.env.VERIFY_TOKEN; // Analise parâmetros da solicitação de verificação do webhook let mode = req.query["hub.mode"]; let token = req.query["hub.verify_token"]; let challenge = req.query["hub.challenge"]; if (mode && token) { if (mode === "subscribe" && token === verify_token) { console.log("WEBHOOK_VERIFIED"); res.status(200).send(challenge); } else { res.sendStatus(403); } }

A rota POST /webhook processa notificações recebidas de webhook. As solicitações de webhook podem ter cargas úteis diferentes. Por isso, o código abaixo lê a mensagem e o número de telefone da empresa acessando os campos de solicitação com segurança no caso de serem indefinidos.

const message = req.body.entry?.[0]?.changes[0]?.value?.messages?.[0]; const business_phone_number_id =

req.body.entry?.[0].changes?.[0].value?.metadata?.phone_number_id;

Depois, ele verifica se a solicitação recebida é para uma mensagem do tipo "text" que contém a palavra "hora marcada". Se a mensagem contiver a palavra, o fluxo é enviado para o usuário. A mensagem do fluxo é enviada com flow_action: "data_exchange," o que significa que o fluxo fará uma solicitação INIT para o ponto de extremidade quando for iniciado para obter a tela e os dados iniciais.

if ( message.type === "text" && // para fins de demonstração, envie a mensagem do fluxo sempre que um usuário enviar uma mensagem contendo "hora marcada" message.text.body.toLowerCase().includes("appointment") ) { // enviar mensagem de fluxo de acordo com os documentos aqui https://developers.facebook.com/docs/whatsapp/flows/gettingstarted/sendingaflow#interactive-message-parameters await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, type: "interactive", interactive: { type: "flow", header: { type: "text", text: "Olá 👋", }, body: { text: "Tudo pronto para transformar seu espaço? Agente uma consulta personalizada com sua equipe de especialistas!", }, footer: { text: "Clique no botão abaixo para proceder", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // substitua flow_token por um identificador único para que essa mensagem de fluxo o acompanhe no seu ponto de extremidade e webhook flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "Reserve uma hora marcada", flow_action: "data_exchange", }, }, }, }, }); } ...

Se o tipo de mensagem recebida não for "text", o código verifica se o tipo de mensagem é "interactive." Um tipo interativo "nfm_reply" indica que a mensagem de entrada é uma resposta do fluxo. Depois, ele retorna uma mensagem "Você reservou uma hora marcada com sucesso" para o usuário.

... if ( message.type === "interactive" && message.interactive?.type === "nfm_reply" ) { // send confirmation message await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, text: { body: "You've successfully booked an appointment" }, }, }); } ...

Depois, marca a mensagem recebida como lida para que o usuário veja os tiques azuis.

... // marque a mensagem recebida como lida await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", status: "read", message_id: message.id, }, }); ...
        

A experiência do usuário

Neste exemplo, o usuário envia uma mensagem para seu número contendo a palavra "hora marcada" e depois recebe a mensagem do fluxo. Você também pode optar por enviar o fluxo após uma interação diferente ou como um modelo de mensagem.

O usuário receberá uma mensagem do fluxo com um botão de CTA para reservar uma hora marcada, onde poderá preencher com seus dados. Depois, receberá uma mensagem de confirmação, onde concluirá o fluxo.

Gráfico de envio do fluxo para o usuário

Neste guia, você aprender a configurar um fluxo do WhatsApp para uma reserva de horas marcadas descomplicada. Com o Flow Builder UI, você cria um formulário para coletar detalhes dos usuários referentes à hora marcada.

Os fluxos eliminam a necessidade de redirecionar usuários para um site externo para reservas de horas marcadas, melhorando a experiência do cliente. O processo objetivo permite que os usuários façam reservas diretamente no WhatsApp. Além de reservar horas marcadas, você pode usar o WhatsApp Flows para coletar feedback do serviço de atendimento ao cliente ou ajudar os usuários a se cadastrarem para promoções ou listas de correspondência. O WhatsApp Flows também oferece a flexibilidade de se conectar com as APIs externas ou outros apps no seu ponto de extremidade.

Criar um WhatsApp Flows é fácil com o Flow Builder UI. No entanto, você também pode usar a API de fluxo para criar fluxos programaticamente. Para obter mais informações, consulte a documentação do WhatsApp Flows.