Accueil / Articles

Envoyer et recevoir des SMS en Node.js à l’aide de Twilio Webhooks et Express

Créez un petit serveur Express qui envoie des textes via Twilio, reçoit les réponses par l’intermédiaire d’un webhook exposé avec ngrok, et y répond automatiquement à l’aide de TwiML.

Éditeur : REACTAPP.TOP6 min de lecture1483 mots

Sources: **Build a Two-Way SMS with Node.js and Twilio (Full Code Included)**

La plupart des tutoriels sur les SMS s’arrêtent dès que le message quitte votre serveur. La partie vraiment utile concerne les messages entrants : détecter que l’utilisateur a répondu, lire son message et y répondre. Ce cycle est à la base des flux OTP, des bots d’assistance et des rappels de rendez-vous que les gens confirment par un simple mot. À la fin de cette démarche, vous disposerez d’un serveur Express capable d’envoyer des messages sur un téléphone et de réagir chaque fois que ce dernier répond.

Prérequis

  • Un compte Twilio. Au moment de la rédaction, l’essai gratuit inclut environ 15 dollars en crédit, suffisant pour expérimenter.
  • Postman ou tout autre client HTTP.
  • Node.js installé localement.
  • ngrok, qui fournit à Twilio une adresse publique redirigeant vers votre ordinateur portable.

Un serveur Express minimal

Créez un dossier contenant un fichier server.js. Le serveur se connecte à localhost sur le port 3000, active la lecture des corps de message en JSON et codés URL, et expose une route de vérification de fonctionnement sous /api/v1/. Le parseur de codes URL est essentiel : Twilio envoie les messages reçus sous forme de corps POST codés en format formulaire, donc sans lui req.body serait vide par la suite.

const express = require("express");
const bodyParser = require("body-parser");
const hostname = "127.0.0.1";
const port = 3000;
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const baseUrl = "/api/v1/";

app.get(`${baseUrl}`, (req, res) => {
  res.status(200).json({ message: "Hello World" });
});
app.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Installez les dépendances nécessaires :

npm init -y
npm install express body-parser

Exécutez node server.js et ouvrez http://127.0.0.1:3000/api/v1/ ; une réponse « Hello World » confirme que tout fonctionne correctement. Express 4.16+ inclut également express.json() et express.urlencoded(), ce qui rend body-parser optionnel dans les nouveaux projets.

Exposer localhost avec ngrok

Twilio fonctionne sur Internet public tandis que votre serveur se trouve sur une machine privée, ce qui empêche Twilio d’y accéder directement. ngrok crée un tunnel et vous fournit une URL HTTPS publique redirigeant vers un port local. Heroku était autrefois l’option gratuite pour cela, mais son plan gratuit a disparu.

Installez et authentifiez ngrok en utilisant les documents ngrok, puis ajoutez un script dans package.json:

"scripts": {
  "ngrok": "ngrok http http://127.0.0.1:3000/"
}

Gardez le serveur en cours d’exécution dans un terminal, puis lancez le tunnel dans un autre :

npm run ngrok

ngrok affiche une adresse telle que https://abcd-1234.ngrok-free.app. Laissez ce terminal ouvert.

Faites attention : avec le plan gratuit, l’URL générée change généralement à chaque redémarrage de ngrok. Lorsqu’un webhook fonctionnel cesse soudainement de répondre, c’est souvent à cause d’une URL obsolète dans la console Twilio ; mettez-la donc à jour après chaque redémarrage.

Configuration de Twilio

Dans le tableau de bord de la console, copiez votre SID de compte et votre token d’authentification dans un fichier .env. Ce token est confidentiel : ne le commitez jamais, et ajoutez .env à .gitignore.

ACCOUNT_SID_TWILIO=your_account_sid
AUTH_TOKEN_TWILIO=your_auth_token

Vous pouvez fixer des produits dans la navigation latérale ; seuls Messaging et Phone Numbers sont importants ici.

Récupération d’un numéro de téléphone

  1. Ouvrez Phone Numbers, puis Acheter un numéro.
  2. Sélectionnez n’importe quel numéro disponible ; les numéros américains sont généralement les moins chers.
  3. Le coût est déduit du solde d’essai et le numéro apparaît parmi vos numéros actifs.

Création d’un service de messagerie

  1. Sous Messaging, créez un nouveau Messaging Service.
  2. Au stade du Sender Pool, ajoutez votre numéro.
  • Dans l’étape Integration, choisissez Envoyer un webhook et configurez à la fois URL de demande et URL de secours avec votre adresse ngrok, en y ajoutant le chemin /api/v1/sms. Ce routage est détaillé ci-dessous.
  • Sautez l’étape optionnelle et terminez.
  • Stockez le Messaging Service SID ainsi que votre numéro dans .env :

    FROM_TWILIO=+1XXXXXXXXXX
    MESSAGING_SERVICE_SID_TWILIO=your_messaging_service_sid
    

    Intégration du SDK Twilio dans Node

    Installez le SDK ainsi que dotenv, qui charge les valeurs de .env dans process.env :

    npm install twilio dotenv
    

    Placez ce code en haut de server.js. L’ordre est important : le client lit les identifiants lors de sa création, donc si dotenv est chargé plus tard, les deux valeurs seront undefined et toutes les tentatives d’authentification échoueront.

    require("dotenv").config();
    
    const client = require("twilio")(
      process.env.ACCOUNT_SID_TWILIO,
      process.env.AUTH_TOKEN_TWILIO
    );
    

    Envoi d’un message texte sortant

    Cette route appelle client.messages.create() et rapporte le résultat sous forme de JSON. Le bloc try/catch renvoie les erreurs Twilio, telles qu’un numéro mal formaté, à l’appelant au lieu de faire planter le processus.

    app.post(`${baseUrl}sendSms`, async (req, res) => {
      try {
        await client.messages.create({
          body: "Hello",
          from: process.env.FROM_TWILIO,
          messagingServiceSid: process.env.MESSAGING_SERVICE_SID_TWILIO,
          to: "+92XXXXXXXXXX", // whoever you're texting
        });
        res.status(200).json({ message: "Text sent successfully" });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    

    Signification des champs :

    • body : le texte du message.
    • from : votre numéro Twilio.
    • messagingServiceSid : lie le message au service et à son pool d’expéditeurs.
    • to : le destinataire au format E.164 (+1..., +92...).

    Appelez POST /api/v1/sendSms depuis Postman et le téléphone devrait recevoir « Hello ». Dans du code réel, lisez to et body à partir de la requête et validez-les plutôt que de les coder en dur.

    Les comptes d’essai ne peuvent envoyer de messages que vers des numéros vérifiés dans la console Twilio. Si la demande réussit mais qu’aucun message n’arrive, vérifiez le destinataire ou effectuez une mise à niveau.

    Réception des réponses via le webhook

    Lorsqu’une personne envoie un message à votre numéro Twilio, Twilio envoie le message au webhook configuré dans le Messaging Service. Votre serveur le reçoit et répond avec du TwiML, le format XML de Twilio utilisé pour décrire l’action suivante. Importez l’outil d’aide avec les autres éléments requis :

    const MessagingResponse = require("twilio").twiml.MessagingResponse;
    

    Ensuite, ajoutez l’écouteur. L’erreur classique est une incohérence de chemin : le route doit correspondre au chemin saisi dans la console, ici /api/v1/sms. Le gestionnaire enregistre req.body.Body et renvoie un document TwiML vide au format text/xml, ce qui signifie qu’aucune réponse n’est envoyée.

    app.post(`${baseUrl}sms`, (req, res) => {
      // Fires every time someone replies to your number
      const twiml = new MessagingResponse();
    console.log("INCOMING ----->", req.body.Body);
      res.writeHead(200, { "Content-Type": "text/xml" });
      res.end(twiml.toString());
    });
    

    Entrez votre numéro Twilio et le message apparaîtra dans la terminal ; la boucle est alors fermée. La charge utile contient également l’élément From, qui indique le numéro de l’expéditeur, nécessaire pour suivre les conversations par utilisateur.

    Puisque l’endpoint est public, quiconque trouve l’URL peut envoyer des messages falsifiés. Twilio signe chaque webhook avec votre token d’authentification dans l’en-tête X-Twilio-Signature, et le SDK Node inclut des outils de validation. Activez-les avant de déployer en dehors d’un test local.

    Réponse automatique

    Le serveur écoute mais reste silencieux. Ajouter un message à la réponse TwiML le transforme en bot basé sur des mots-clés. Placez ce code à l’intérieur du gestionnaire /sms après la création de twiml ; il traite le texte en supprimant les espaces inutiles et en le mettant en minuscules avant de choisir une réponse :

    const incoming = req.body.Body.trim().toLowerCase();
    if (incoming === "hi") {
      twiml.message("Hey there! How can I help?");
    } else {
      twiml.message("Hmm, I didn't catch that.");
    }
    

    Twilio envoie la réponse retournée à l’expéditeur, il n’est donc pas nécessaire d’appeler à nouveau messages.create().

    Le serveur complet

    Tout est dans un seul fichier, avec la réponse automatique commentée :

    require("dotenv").config();
    
    const express = require("express");
    const bodyParser = require("body-parser");
    const MessagingResponse = require("twilio").twiml.MessagingResponse;
    const client = require("twilio")(
      process.env.ACCOUNT_SID_TWILIO,
      process.env.AUTH_TOKEN_TWILIO
    );
    const hostname = "127.0.0.1";
    const port = 3000;
    const app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    const baseUrl = "/api/v1/";
    // Health check
    app.get(`${baseUrl}`, (req, res) => {
      res.status(200).json({ message: "Hello World" });
    });
    
    // Goal 1: Send an SMS
    app.post(`${baseUrl}sendSms`, async (req, res) => {
      try {
        await client.messages.create({
          body: "Hello",
          from: process.env.FROM_TWILIO,
          messagingServiceSid: process.env.MESSAGING_SERVICE_SID_TWILIO,
          to: "+92XXXXXXXXXX", // whoever you're texting
        });
        res.status(200).json({ message: "Text sent successfully" });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    
    // Goal 2: Receive replies (this endpoint must match your Twilio webhook)
    app.post(`${baseUrl}sms`, (req, res) => {
      const twiml = new MessagingResponse();
      console.log("INCOMING ----->", req.body.Body);
      // Optional auto-reply:
      // const incoming = req.body.Body.trim().toLowerCase();
      // if (incoming === "hi") {
      //   twiml.message("Hey there! How can I help?");
      // } else {
      //   twiml.message("Hmm, I didn't catch that.");
      // }
      res.writeHead(200, { "Content-Type": "text/xml" });
      res.end(twiml.toString());
    });
    
    app.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
    

    Points clés

    • Les SMS sortants nécessitent une seule appel SDK ; les SMS entrants utilisent un webhook, donc le serveur doit être accessible et comprendre TwiML.
    • Twilio envoie des données codées en format formulaire, d’où le besoin d’un analyseur de URL encodée.
    • Le chemin du webhook dans la console et le route d’Express doivent correspondre, et une réinitialisation du tunnel gratuit entraîne un nouveau URL.
    • Les échecs de test silencieux sont souvent dus à la restriction des numéros vérifiés, et non à une erreur.
    • Vérifiez les signatures et supprimez les valeurs codées en dur avant utilisation réelle.

    Ici, vous pouvez conserver les conversations, développer le bot basé sur des mots-clés ou ajouter un système d’authentification par code OTP. Pour en savoir plus sur les schémas liés aux appels de retour et aux tentatives répétées, consultez six schémas d’intégration pour relier de manière fiable des services Node.js.