Torna alle notizie per sviluppatori

Prenotazione di appuntamenti tramite WhatsApp Flows: creazione di un back-end Node.js

27 febbraio 2024DiGafi G & Iryna Wagner

Con WhatsApp Flows puoi creare messaggi interattivi che consentano agli utenti di eseguire azioni direttamente su WhatsApp. I flussi ti permettono di creare schermate per l'interazione con l'utente. Ad esempio, puoi creare semplici moduli di input per raccogliere contatti o inviare recensioni. Puoi inoltre progettare flussi complessi su più schermate per programmare appuntamenti.

Questa guida illustra come creare un'app Node.js per permettere agli utenti di prendere appuntamenti tramite WhatsApp Flows. Creerai un flusso sulla piattaforma WhatsApp Business, quindi configurerai un webhook per ricevere la risposta del flusso e fissare l'appuntamento.

Prerequisiti

Ecco cosa ti serve per seguire questo tutorial:

Creazione di un flusso di WhatsApp

Sono disponibili due modi per creare un flusso di WhatsApp: Flows Builder, accessibile tramite WhatsApp Manager, e l'API Flows. In questo tutorial viene utilizzato Flows Builder.

Creazione di un flusso

Nel menu a sinistra della dashboard di WhatsApp Manager, seleziona Strumenti per l'account. A questo punto, clicca su Flussi.

Grafica di WhatsApp Manager

Clicca su Crea flusso, in alto a destra.

grafica della creazione del flusso

Nella finestra di dialogo visualizzata, inserisci i dettagli del flusso di appuntamento:

  • Nome: digita PrenotazioneAppuntamento o scegli il nome che preferisci.
  • Categorie: seleziona Prenotazione di appuntamenti.
  • Modello: scegli Fissa un appuntamento. Userai il modello perché contiene gli elementi necessari per fissare un appuntamento, come le schermate per i dettagli dell'appuntamento, per l'inserimento dei dati dell'utente, per il riepilogo dell'appuntamento e per la visualizzazione delle condizioni dell'azienda. Puoi personalizzare ulteriormente il modello a seconda alle necessità.
Grafica della prenotazione di un appuntamento

Clicca su Invia per creare il flusso.

Puoi visualizzare l'anteprima del flusso a destra della Builder UI. La schermata dell'appuntamento consente all'utente di scegliere i dettagli dell'appuntamento, come luogo e data. Nella schermata dei dettagli l'utente inserirà le proprie informazioni. La schermata del riepilogo mostra il riepilogo della prenotazione. L'ultima schermata mostra le condizioni dell'azienda.

Durante la modifica, il flusso rimane nello stato di bozza. Puoi condividerlo con il tuo team solo a scopo di test. Per condividerlo con un pubblico più ampio, dovrai pubblicarlo. Tuttavia, una volta pubblicato non potrai più modificarlo. Poiché devi ancora inserire l'URL dell'endpoint per questo flusso di appuntamento, per il momento lascialo nello stato di bozza e procedi con il passaggio successivo, nel quale configurerai l'endpoint.

Configurazione dell'endpoint del flusso

WhatsApp Flows consente la connessione a un endpoint esterno. Questo endpoint può fornire dati dinamici per il routing di controllo e del flusso. Riceve inoltre le risposte inviate dall'utente tramite il flusso.

A scopo di test, in questo articolo viene usato Glitch per ospitare l'endpoint. L'utilizzo di Glitch è facoltativo e non è necessario per usare i flussi. Puoi clonare il codice dell'endpoint da GitHub ed eseguirlo nell'ambiente che preferisci.

Accedi al codice dell'endpoint in Glitch e modificalo per ottenere un dominio unico. Per modificarlo, clicca su Modifica nella parte superiore della pagina. Sarà visualizzato un dominio unico come segnaposto nell'elemento di input sulla destra della pagina di Glitch.

Prima di procedere, esaminiamo il codice. Sono presenti quattro file JavaScript nella directory src: encryption.js, flow.js, keyGenerator.js e server.js. Il file di ingresso è server.js, quindi esaminiamolo per primo.

server.js

Il file server.js inizia con la configurazione dell'applicazione Express per usare il middleware express.json per analizzare le richieste JSON in arrivo. A questo punto, carica le variabili ambientali necessarie per l'endpoint.

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

APP_SECRET è usato per la verifica della firma. Ti aiuta a controllare se un messaggio arriva tramite WhatsApp e quindi può essere elaborato in modo sicuro. Lo aggiungerai al file .env.

Per accedere al tuo APP_SECRET, vai alla dashboard dell'app su Meta for Developers. Nel pannello di navigazione a sinistra, sotto Impostazioni app, scegli Base. Clicca su Mostra sotto Chiave segreta e copiala. Quindi, torna a Glitch, apri il file .env e crea una variabile denominata APP_SECRET con la chiave copiata.

PRIVATE_KEY aiuta a decodificare i messaggi ricevuti. Il PASSPHRASE sarà usato per verificare la chiave privata. Oltre alla chiave privata, ti serve anche la chiave pubblica corrispondente, che caricherai successivamente. Non usare mai le chiavi private per i tuoi account di produzione qui. Crea una chiave privata temporanea per eseguire test su Glitch, poi sostituiscila con la chiave di produzione nella tua infrastruttura.

Genera una coppia di chiavi pubblica-privata eseguendo il seguente comando nel terminale Glitch. Sostituisci <your-passphrase> con la frase di accesso designata. Accedi al terminale Glitch cliccando sulla tab TERMINALE in fondo alla pagina.

node src/keyGenerator.js <your-passphrase>

Copia la frase di accesso e la chiave privata e incollale sul file .env. Clicca sul file denominato .env nella barra laterale a sinistra, quindi clicca su ✏️ Testo semplice in alto. Non modificarlo direttamente dall'interfaccia utente, poiché danneggerebbe la formattazione della chiave.

Dopo aver impostato le variabili ambientali, copia la chiave pubblica che hai generato e caricala tramite l'API Graph.

Il file server.js contiene anche un endpoint POST che esegue diversi passaggi:

  • Verifica che la chiave privata sia presente:
       if (!PRIVATE_KEY) { throw new Error('Private key is empty. Please check your env variable "PRIVATE_KEY".'); }
  • Convalida la firma della richiesta usando la funzione isRequestSignatureValid trovata in fondo al file:
if(!isRequestSignatureValid(req)) { // Return status code 432 if request signature does not match. // To learn more about return error codes visit: https://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codes return res.status(432).send(); }
  • Decodifica i messaggi in arrivo usando la funzione decryptRequest trovata nel file 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 quale schermata del flusso mostrare all'utente. Vedremo la funzione getNextScreen nel dettaglio più avanti.

const screenResponse = await getNextScreen(decryptedBody);

       console.log("👉 Response to Encrypt:", screenResponse);
  • Crittografa la risposta da inviare all'utente:
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));

encryption.js

Questo file contiene la logica per la crittografia e la decodifica dei messaggi scambiati per motivi di sicurezza. Questo tutorial non tratterà il funzionamento del file.

keyGenerator.js

Questo file consente la generazione di chiavi pubbliche e private, come abbiamo visto in precedenza. Come per il file encryption.js, questo tutorial non esplorerà il file keyGenerator.js nel dettaglio.

flow.js

La logica per la gestione del flusso si trova in questo file. Inizia con un oggetto a cui è stato assegnato il nome SCREEN_RESPONSES. L'oggetto contiene gli ID schermata con i dettagli corrispondenti, come i dati predefiniti usati negli scambi di dati. Questo oggetto è generato dallo Strumento di creazione dei flussi in "..." > Endpoint > Snippet > Risposte. Nello stesso oggetto, è presente anche un altro ID, SUCCESS, che viene inviato nuovamente al dispositivo del cliente al completamento corretto del flusso. In questo modo il flusso sarà chiuso.

La funzione getNextScreen contiene la logica che indica all'endpoint quali dati del flusso mostrare all'utente. Inizia estraendo i dati necessari dal messaggio decodificato.

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

Gli endpoint di WhatsApp Flows ricevono solitamente tre richieste:

Puoi trovare i relativi dettagli nella documentazione relativa all'endpoint.

La funzione gestisce il controllo integrità e le notifiche di errore usando istruzioni if e risponde di conseguenza, come mostrato nello snippet di seguito:

// 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 un utente clicca sul pulsante di call to action (CTA) del flusso, si attiva un'azione INIT. Questa azione restituisce la schermata di appuntamento insieme ai dati. Disabilita inoltre i menu a discesa di luogo, data e ora, in modo che l'utente compili tutti i campi.

Ad esempio, il menu a discesa della data viene abilitato solo una volta compilato quello del luogo. L'abilitazione e la disabilitazione dei campi sono gestiti quando viene ricevuta una richiesta data_exchange.

// handle initial request when opening the flow and display APPOINTMENT screen if (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // these fields are disabled initially. Each field is enabled when previous fields are selected is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }

Per le azioni data_exchange, viene usata una struttura switch case per determinare quali dati inviare in base all'ID schermata. Se l'ID schermata è APPOINTMENT, i campi del menu a discesa sono abilitati solo quando i precedenti menu a discesa sono selezionati.

// Each field is enabled only when previous fields are selected 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)

Per la schermata DETAILS, i titoli delle proprietà dell'oggetto dati, come luogo e reparto, sono estratti dall'oggetto SCREEN_RESPONSES.APPOINTMENT.data. Questo codice presuppone che ci sia una corrispondenza valida, quindi potrebbe generare un errore se non viene trovato alcun oggetto corrispondente.

A questo punto, prendi un'istanza dell'oggetto posizione. La selezione dello specifico oggetto posizione è determinata dalla corrispondenza della proprietà id degli oggetti nell'array con il valore di 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;

I valori sono quindi concatenati e restituiti nella risposta per la visualizzazione della schermata SUMMARY.

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, }, };
        

Dopo l'invio della schermata SUMMARY da parte del cliente, viene inviata una risposta di successo al dispositivo del cliente per contrassegnare il flusso come completato. Il flow_token è un identificatore univoco che puoi impostare quando invii il flusso all'utente.

// send success response to complete and close the flow return { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };

La schermata TERMS non contiene dati da scambiare, quindi non viene gestita dall'endpoint.

Aggiunta dell'endpoint al flusso

In alto a destra della pagina di Glitch, puoi copiare l'URL cliccando sull'icona del menu con i tre puntini e selezionando Copia link. Puoi anche ottenere il link cliccando su Condividi in alto a destra.

Accedi all'editor del flusso. Clicca su Configura nel banner marrone che appare nella parte superiore dell'editor.

Verrà visualizzato un pop-up, che ti consentirà di configurare l'URI endpoint, il numero di telefono aziendale e l'app su Meta for Developers. Una volta completate le configurazioni necessarie esegui un controllo integrità. Innanzitutto, esegui l'anteprima interattiva e assicurati di selezionare Richiedi dati, sotto Richiedi dati nella prima schermata, nelle impostazioni dell'anteprima interattiva. Questo invia all'endpoint la richiesta di recuperare i dati per la prima schermata, verificando che l'endpoint sia disponibile e che sia stato implementato un controllo integrità.

Quindi, pubblica il flusso cliccando sul menu con i tre puntini (...) e scegliendo Pubblica. Questa operazione invierà una richiesta di controllo integrità al tuo endpoint con action === "ping" per verificare che l'endpoint sia configurato prima della pubblicazione.

Grafica dell'endpoint

Test del flusso

Dopo aver completato le configurazioni, attiva nuovamente l'anteprima interattiva nella Builder UI di WhatsApp per testare il flusso. Nel pop-up che viene visualizzato, seleziona il numero di telefono e scegli l'opzione Richiedi dati sotto Richiedi dati nella prima schermata. Scegli il flusso cliccando sull'icona X per ricominciare a testare il flusso dal pulsante CTA.

Apri il registro di Glitch cliccando sulla tab REGISTRI. Cancellalo cliccando su Cancella. Quindi torna all'anteprima della Builder UI di WhatsApp. Clicca su Anteprima del flusso. Vedrai qualcosa di simile:

Grafica dell'anteprima del flusso

Ora torna ai registri di Glitch. Vedrai un'azione INIT, il token del flusso e altri dettagli sotto la richiesta decodificata. È presente anche una risposta da crittografare inviata al flusso dell'utente una volta selezionato il menu a discesa del reparto.

grafica della richiesta decodificata

Procedi alla selezione del reparto. Osserva come is_location_enabled è impostato su true e l'azione è stata modificata in data_exchange.

grafica di data_exchange

Continua a testare il flusso e osserva le modifiche dei dati nei registri di Glitch. Registri simili saranno generati quando gli utenti interagiranno con il flusso dai propri dispositivi mobili.

Nella prossima sezione, creerai un webhook che invia un messaggio di conferma all'utente quando prenota un appuntamento.

Configurazione del webhook

Quando un utente completa il flusso, al webhook per cui è stata attivata l'iscrizione viene inviato un messaggio che contrassegna il flusso come completato. Da questo webhook avviserai l'utente che la prenotazione dell'appuntamento è avvenuta con successo con un messaggio nella chat. Come per l'endpoint, userai anche Glitch per effettuare test. Puoi accedere al codice e modificarlo qui.

L'utilizzo di Glitch è facoltativo e non è necessario per usare i flussi. Puoi clonare il codice del webhook da GitHub ed eseguirlo nell'ambiente che preferisci.

Impostazione delle variabili ambientali

Per impostare le variabili ambientali, apri il file .env su Glitch. Imposta VERIFY_TOKEN sulla stringa che preferisci, FLOW_ID con l'ID del flusso e GRAPH_API_TOKEN sul token d'accesso del tuo account WhatsApp Business. Puoi ottenere il token d'accesso dalla dashboard della tua app su Meta for Developers cliccando su Configurazione API nella sezione WhatsApp del pannello di navigazione a sinistra.

grafica della configurazione dell'API

Nella pagina visualizzata, clicca sul pulsante Copia sotto la scheda Token d'accesso temporaneo. Incolla la chiave nel tuo file .env.

Iscrizione del webhook sulla dashboard di Meta

Nel tuo account su Meta for Developers, clicca sulla voce Configurazione del menu WhatsApp nel pannello di navigazione a sinistra.

grafica della configurazione

Nella scheda Webhook, clicca su Modifica. Nella finestra di dialogo che si apre, incolla l'URL di Glitch copiato, allegandovi /webhook nel campo URL di callback. Per il campo Verifica il token, aggiungi il token dalla variabile VERIFY_TOKEN nel file .env. Al termine, clicca su Verifica e salva. La finestra di dialogo si chiuderà e si tornerà alla schermata principale. Clicca su Gestisci e controlla il campo messaggi. Adesso il tuo webhook è pronto.

Guida al codice webhook

Il codice contiene due percorsi: POST /webhook e GET /webhook. Il percorso GET gestisce le richieste di verifica del webhook controllando il token fornito con un token di verifica predefinito e rispondendo con i codici di stato appropriati e un token di sfida.

const verify_token = process.env.VERIFY_TOKEN; // Parse params from the webhook verification request 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); } }

Il percorso POST /webhook elabora le notifiche webhook in arrivo. Le richieste webhook possono avere diversi payload. Il codice seguente, quindi, legge il messaggio e il numero di telefono aziendale accedendo ai campi di richiesta in modo sicuro nel caso in cui siano indefiniti.

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;

Quindi controlla se la richiesta in entrata è per un messaggio di tipo "text" che contiene la parola "appuntamento". Se il messaggio contiene questa parola, il flusso viene mandato all'utente. Il messaggio di flusso viene mandato con flow_action: "data_exchange," che vuol dire che il flusso manderà una richiesta INIT all'endpoint quando viene avviato per ottenere la schermata iniziale e i dati.

if ( message.type === "text" && // for demo purposes, send the flow message whenever a user sends a message containing "appointment" message.text.body.toLowerCase().includes("appointment") ) { // send flow message as per the docs here 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: "Hello there 👋", }, body: { text: "Ready to transform your space? Schedule a personalized consultation with our expert team!", }, footer: { text: "Click the button below to proceed", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // replace flow_token with a unique identifier for this flow message to track it in your endpoint & webhook flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "Book an appointment", flow_action: "data_exchange", }, }, }, }, }); } ...

Se il tipo di messaggio in entrata non è "text", il codice controlla se il tipo di messaggio è "interactive." Un tipo interattivo "nfm_reply" indica che il messaggio in entrata è una risposta del flusso. Restituisce quindi all'utente il messaggio "Hai prenotato correttamente un appuntamento".

... 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" }, }, }); } ...

Quindi contrassegna il messaggio in entrata come letto, in modo che l'utente veda le spunte blu.

... // mark incoming message as read 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, }, }); ...
        

L'esperienza utente

In questo esempio, l'utente invia al tuo numero un messaggio contenente la parola "appuntamento" e riceve il messaggio di flusso. Puoi anche scegliere di inviare il flusso dopo un'interazione diversa o come un modello di messaggio.

L'utente riceverà un messaggio di flusso con un pulsante CTA per prenotare l'appuntamento, in cui può inserire le proprie informazioni. Quindi, al completamento del flusso riceverà un messaggio di conferma.

Grafica dell'invio del flusso all'utente

In questa guida scoprirai come configurare un flusso di WhatsApp per una prenotazione di appuntamenti semplice. Usando Flow Builder UI, hai creato un modulo per raccogliere i dettagli degli appuntamenti dagli utenti.

I flussi eliminano la necessità di reindirizzare gli utenti a un sito web esterno per la prenotazione di appuntamenti, migliorando l'esperienza del cliente. La semplicità del processo consente agli utenti di completare le prenotazioni su WhatsApp. Oltre alla prenotazione di appuntamenti, puoi usare WhatsApp Flows per raccogliere feedback sul servizio clienti o per aiutare gli utenti a iscriversi a promozioni o mailing list. WhatsApp Flows offre inoltre la flessibilità di connettersi con API esterne o altre app nel tuo endpoint.

Creare WhatsApp Flows è semplice con Flow Builder UI. Tuttavia, puoi anche usare l'API Flow per creare i flussi in modo programmatico. Per ulteriori informazioni, consulta la documentazione di WhatsApp Flows.