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.
Ecco cosa ti serve per seguire questo tutorial:
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.
Nel menu a sinistra della dashboard di WhatsApp Manager, seleziona Strumenti per l'account. A questo punto, clicca su Flussi.
Clicca su Crea flusso, in alto a destra.
Nella finestra di dialogo visualizzata, inserisci i dettagli del flusso di 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.
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.
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:
if (!PRIVATE_KEY) { throw new Error('Private key is empty. Please check your env variable "PRIVATE_KEY".'); }
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(); }
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);
getNextScreen
nel dettaglio più avanti. const screenResponse = await getNextScreen(decryptedBody);
console.log("👉 Response to Encrypt:", screenResponse);
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));
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.
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.
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:
data_exchange
data.error
ping
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.
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.
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:
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.
Procedi alla selezione del reparto. Osserva come is_location_enabled
è impostato su true
e l'azione è stata modificata in 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.
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.
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.
Nella pagina visualizzata, clicca sul pulsante Copia sotto la scheda Token d'accesso temporaneo. Incolla la chiave nel tuo file .env
.
Nel tuo account su Meta for Developers, clicca sulla voce Configurazione del menu WhatsApp nel pannello di navigazione a sinistra.
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.
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, }, }); ...
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.
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.