https://a.storyblok.com/f/270183/1368x665/5912b74ed1/laravel-nightwatch.jpg

Surveillez vos Webhooks avec Laravel Nightwatch

Publié le November 26, 2025

Temps de lecture : 12 minutes

Il existe une tonne de façons de surveiller votre application, et en tant que développeur, cela peut être décourageant. Pour les développeurs PHP, vous avez la possibilité d'utiliser Blackfire et Tideways au sein de l'espace PHP ou des plateformes cloud externes telles que Datadog, Sentry, Papertrail ou New Relic. Les quatre derniers exemples relèvent de la surveillance des performances des applications, mais ce sont tous des tiers qui se concentrent sur ce que font leurs agents de reporting au sein du cloud. Cela ne nous donne pas beaucoup d'informations sur notre codeà moins, bien sûr, que vous n'ayez écrit le code de votre application d'entreprise ou de mise à l'échelle de manière à tout enregistrer de manière efficace.

J'ai trouvé la mise en place d'agents de surveillance comme celui-ci particulièrement pénible et peut-être un peu trop complexe par nécessité. Cet article a pour but de vous montrer comment Laravel a éliminé la majeure partie de la complexité. Laravel Nightwatch avec un code de requête de serveur fictif pour le tester.

Lorsque vous exécutez des webhooks en production, il est facile que les choses tournent mal : les messages échouent, les charges utiles changent ou les réponses ralentissent. En tant que développeurs, nous nous tournons vers les outils de surveillance pour rester à l'affût de ces problèmes, mais le paysage peut sembler décourageant. Rien que dans le monde PHP, il y a Blackfire et Tideways, puis les poids lourds APM comme Datadog, Sentry, Papertrail ou New Relic. Ces outils sont puissants, mais ce sont des agents tiers qui se concentrent sur le reporting côté cloud, ce qui ne nous donne pas toujours la vision que nous voulons de ce que fait réellement notre code Laravel.

Et pour être honnête, la mise en place de certains de ces agents est... pénible. Ils fonctionnent, mais ils donnent souvent l'impression d'être trop complexes par nécessité.

Laravel Nightwatch, lancé cette année, vise à changer cela. Il offre aux développeurs PHP un moyen intégré et natif de Laravel de surveiller les événements et les performances de l'application sans les problèmes de configuration habituels. Dans ce tutoriel, nous allons créer un récepteur webhook simple en utilisant les messages RCS de Vonage, envoyer du trafic fictif à travers lui, et regarder tout cela s'animer dans le tableau de bord de Nightwatch.

Laravel a pris en charge la plupart des tâches lourdes de la surveillance des applications. Voyons ce que nous pouvons faire en quelques étapes.

TLDR ; Vous pouvez trouver le code du projet ici.

Conditions préalables

Configurer l'application Laravel

Nous construisons une application de surveillance, mais nous avons besoin de quelque chose à surveiller ! Pour cela, nous allons construire une application avec In-App Messaging. La messagerie RCS est un tout nouveau protocole permettant d'envoyer des messages de type SMS, mais avec beaucoup plus de possibilités. Vous pouvez envoyer et recevoir des messages RCS en utilisant l Vonage Messages API et une Applications Vonage, tout comme pour les SMS. Ce qui différencie vraiment RCS, c'est ce qu'il peut délivrer : des médias de meilleure qualité (audio, images, vidéo), des fichiers plus volumineux et des éléments interactifs plus riches tels que les réponses suggérées, les actions suggérées, les cartes et les carrousels.

Lorsqu'un utilisateur final répond sur son appareil, une application RCS fonctionnelle sera configurée pour envoyer des Webhooks à une URL désignée afin de les consommer.

Le raccourci que nous pouvons prendre ici est que nous n'avons pas besoin de faire toute cette configuration : ce que je veux montrer, c'est le tableau de bord Nightwatch configuré et recevant les données de l'application.

En utilisant le programme d'installation de Laravel (qui s'articule autour du programme d'installation de Composer), créez une nouvelle application Laravel dans votre terminal. create-project), créez une nouvelle application Laravel dans votre terminal :

laravel new tutorials-rcs-webhooks_laravel_nightwatch

Vous pouvez ignorer les options d'échafaudage, sauf qu'il vaut probablement la peine de choisir SQLite comme base de données, car il est facile à mettre en place.

Pour apprendre à démarrer avec SQLite, jetez un coup d'œil à mon article précédent "Le retour de SQLite".

Une fois le processus terminé, chargez votre nouvelle application Laravel dans l'IDE de votre choix. Vous voudrez avoir la possibilité de voir les enregistrements dans votre base de données, donc si vous utilisez quelque chose comme VSCode, ayez un plugin SQL, ou si vous utilisez PHPStorm, configurez la source de la base de données.

Cette application aura une entité, Webhookqui représente le modèle des données entrantes. Quelles sont les données entrantes ? Il s'agit d'un RCS Webhook de Vonage (ou d'un mocking). Si vous jetez un coup d'œil à la spécification OpenAPI, vous trouverez un exemple fictif :

{

   "channel" : "rcs",
   "message_uuid" : "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab",
   "to" : "Vonage",
   "from" : "447700900001",
   "timestamp" : "2025-02-03T12:14:25Z",
   "context_status" : "none",
   "message_type" : "text",
   "text" : "Texte du message entrant".
}

Modèles et migrations

Nous aurons besoin d'un modèle et d'une migration pour créer cela, ce que vous pouvez faire en une seule fois à partir de la ligne de commande :

php artisan make:model Webhook --migration

Avec le commutateur --migration la commande créera à la fois le modèle et la migration. Le monde de l'IA a rendu les choses un peu moins laborieuses de nos jours, donc pour sortir mon code de migration, j'ai jeté ce JSON à un agent IA et lui ai dit "génère le code de migration pour ce JSON", ce qu'il a fait promptement (et étonnamment sans erreurs !). Voici à quoi ressemble cette migration :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
   /**
    * Run the migrations.
    */
   public function up(): void
   {
       Schema::create('webhooks', function (Blueprint $table) {
           $table->id();
           $table->string('channel');
           $table->uuid('message_uuid')->unique();
           $table->string('to');
           $table->string('from');
           $table->timestamp('timestamp');
           $table->string('context_status')->nullable();
           $table->string('message_type')->nullable();
           $table->text('text')->nullable();
           $table->timestamps();
       });
   }

   /**
    * Reverse the migrations.
    */

   public function down(): void
   {
       Schema::dropIfExists('webhook_messages');
   }
};

Jusqu'ici, tout va bien. Ce modèle va être hydraté par un contrôleur, donc pour le faire le plus rapidement possible, vous devrez modifier le modèle pour rendre toutes les propriétés fillable. Allez dans votre app\Models\Webhook.php et modifier le modèle comme suit :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Webhook extends Model
{
   protected $fillable = [
       'channel',
       'message_uuid',
       'to',
       'from',
       'timestamp',
       'context_status',
       'message_type',
       'text',
   ];

   protected $casts = [
       'timestamp' => 'datetime',
   ];
}

Excellent, le modèle peut maintenant être rempli automatiquement avec les données entrantes (et vous n'avez pas besoin d'effectuer une vérification sur les données entrantes, parce que vous faites confiance à Vonage, n'est-ce pas ? Mais sérieusement dans la production, validez toujours vos entrées).

Établir l'itinéraire et le contrôleur

Pour traiter les données entrantes, nous avons besoin d'une route liée à une méthode du contrôleur. Vous pouvez écrire cette logique directement dans le fichier web.php comme une fermeture, mais dans ce tutoriel, je vais la rendre un peu plus traditionnelle.

php artisan make:controller WebhookController

J'ai utilisé le moins de lignes de code possible dans ce contrôleur pour gérer la charge utile :

<?php



namespace App\Http\Controllers;



use App\Models\Webhook;

use Illuminate\Http\Request;

class WebhookController extends Controller
{
   public function handle(Request $request)
   {
       $data = $request->all();

       Webhook::create($data);

       return response('Webhook Handled', 200);
   }
}

Dans nos routes, nous devrons spécifier que la route est liée à la méthode handle() de la méthode WebhookController. La méthode handle() ne fait qu'une seule chose (peut-être la seule fois où j'ai obéi au principe de responsabilité unique !), qui est de prendre le corps de la requête, de l'hydrater en une entité Webhook et de la persister dans la base de données.

Allez à Routes\web.phpet ajoutez l'itinéraire :

Route::post('/webhook', [WebhookController::class, 'handle']);

L'envoi d'une requête à ce point de terminaison (vous pouvez essayer si vous le souhaitez !) ne fonctionnera pas pour le moment. La route est là, le contrôleur est là, mais il ne donnera qu'une réponse 419. C'est parce que j'ai été un mauvais développeur Laravel, mais je peux vous dire comment devenir un bon développeur Laravel. J'essaie de faire cela avec le moins de logique possible, donc je n'ai pas développé l'API boilerplate qui est là si vous la voulez. Parce que cette route est dans web.phpLaravel est configuré pour s'attendre à ce que ce soit GET et POST des requêtes qui compilent du HTML plutôt que de l'API REST JSON en temps réel. Donc, il donne un HTTP 419 parce qu'il s'attend à une sorte de demande de formulaire sous une route de POST, et celles-ci ont un jeton Cross-Site Request Forgery inclus.

La façon la plus rapide et la plus sale de contourner ce problème est de pirater le middleware (ne jamais faire cela en production !). J'ai pensé l'inclure pour montrer un peu ce qui se passe au moment de l'exécution avec Laravel sous le capot. Allez dans le fichier bootstrap de votre framework, situé dans bootstrap\app.php.

Ce fichier sert de point de configuration initial pour votre application, juste après le point d'entrée. Vous pouvez configurer des fichiers de routage personnalisés, des gestionnaires d'erreurs personnalisés au niveau global et des intergiciels personnalisés. Au cours de mes années de développement Laravel, je dirais que c'est probablement une mauvaise idée d'éditer quoi que ce soit au niveau global comme cela, mais j'ai aussi envie de terminer notre requête.

bootstrap\app.php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
   ->withRouting(
       web: DIR.'/../routes/web.php',
       commands: DIR.'/../routes/console.php',
       health: '/up',
   )
   ->withMiddleware(function (Middleware $middleware): void {
       $middleware->validateCsrfTokens(except: [
           '/webhook',
       ]);
   })

   ->withExceptions(function (Exceptions $exceptions): void {
       //
   })->create();

Vous pouvez constater que withMiddleware() utilise la méthode spécifique validateCsrfTokens() pour empêcher toute falsification intersite. Pratique si vous voulez contourner les règles !

Un serveur NodeJS fictif

Dans le monde réel, l'intérêt d'utiliser quelque chose comme Nighwatch est de surveiller les performances de l'application, ce qui signifie nécessairement que vous aurez probablement affaire à un grand nombre de tâches asynchrones dans la file d'attente, beaucoup de mise en cache, beaucoup de journalisation et beaucoup de requêtes. Il est temps de faire semblant jusqu'à ce qu'on le fasse, et j'ai pris le chemin le plus facile pour montrer un tableau de bord qui se remplit : des requêtes.

Il n'y a qu'un seul type de requête entrante, et c'est le Webhook. Par conséquent, nous avons besoin d'un code ou d'un serveur pour générer ces requêtes et les envoyer à notre application pour nous. Il existe toutes sortes d'outils API pour ce genre de choses : Prisma, Postman, Insomnia, Wiremock, etc. Pour citer une de mes philosophies, j'ai opté pour "keep it simple, stupid !". Un fichier JavaScript, agissant en boucle comme un serveur.

A la racine de votre projet, créez un répertoire et nommez-le en conséquence :

mkdir mock_server
cd mock_server
npm init -y
touch mock-webhook-server.js

Ces quatre commandes créent le répertoire, ajoutent la gestion des dépendances et créent le fichier que nous allons exécuter en tant que serveur. Il y a trois choses dont nous avons besoin pour envoyer ces requêtes en boucle :

  • Axiospour l'envoi de requêtes AJAX

  • UUIDpour générer des identifiants uniques pour les webhooks fictifs.

  • Fakerune bibliothèque permettant de générer des données téléphoniques et textuelles factices.

Pour tout installer en une seule fois, entrez ceci dans votre ligne de commande :

npm i axios uuid @faker-js/faker

Voici le code du serveur terminé :

const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const { faker } = require('@faker-js/faker');
const ERROR_RATE = 0.025;
const ERROR_FIELDS = ['message_uuid', 'from', 'timestamp', 'text'];

function generateWebhookPayload() {

   const payload = {
       channel: "rcs",
       message_uuid: uuidv4(),
       to: "Vonage",
       from: faker.phone.number({ style: 'international' }).replace('+', ''),
       timestamp: new Date().toISOString(),
       context_status: "none",
       message_type: "text",
       text: faker.lorem.text().slice(0, 300)
   };

   if (Math.random() < ERROR_RATE) {
       const fieldToRemove = faker.helpers.arrayElement(ERROR_FIELDS);

       delete payload[fieldToRemove];

       console.warn[WARN] Sending malformed webhook (missing '${fieldToRemove}'));
   }
   return payload;
}

function sendWebhook() {
   const payload = generateWebhookPayload();

   axios.post('http://tutorial-voice-rcs_laravel_nightwatch.test/webhook', payload)
       .then(() => {
           console.log[${new Date().toISOString()}] Sent: ${payload.message_uuid});
       })
       .catch(error => {
           console.error[${new Date().toISOString()}] Error: ${error.message});
       });
}

setInterval(sendWebhook, 200);

console.log("Running Mock Webhook Server");

Décompression du code du serveur

Nous avons deux constantes, ERROR_RATE et ERROR_FIELDS. La première est une décimale qui indique la probabilité d'une erreur lors de l'envoi des requêtes, et la seconde indique les champs qui seront supprimés du JSON envoyé et qui provoqueront l'erreur.

generateWebhookPayload() est la méthode de mise en forme de la charge utile JSON qui décidera également si la charge utile est valide ou non. Après tout, il ne faut jamais faire confiance au code de quelqu'un d'autre, n'est-ce pas ? C'est à ce stade nous invoquons la fonction d'aide uuidv4() pour générer l'UUID, et faker pour générer un texte factice d'une longueur maximale de 300 caractères (c'est arbitraire, les messages RCS peuvent contenir jusqu'à 3000 caractères).

Notre astucieuse petite ligne mathématique if (Math.random() < ERROR_RATE) { détermine si elle va supprimer l'un des champs obligatoires. Après tout, Nightwatch sert à visualiser les points problématiques de votre application.

sendWebhook() est la méthode appelée dans une boucle d'événement, qui appelle à faire les données de payload et les envoie ensuite à notre application. Cela se fait juste en dessous de la logique de la méthode, dans la section setInterval(sendWebhook, 200) // <- set this according to how quick you want to generate dummy data

Vous pouvez maintenant l'exécuter, mais n'oubliez pas que votre base de données SQLite va se remplir rapidement. Il est temps de passer à la veille.

Configuration de la veille de nuit

La beauté de Nightwatch est qu'il est gratuit. La version gratuite comprend jusqu'à 300 000 événements d'application enregistrés dans la plateforme en nuage, ce qui est assez facile pour jouer avec.

Rendez-vous sur le site de la Garde de nuit et créez un Account et une Applications. Les quatre étapes sont assez bien documentées. Tout d'abord, donnez un nom à votre application et choisissez une région de stockage adaptée à votre emplacement.

Screenshot showing user account setup for a Nightwatch applicationSetting up your Nightwatch applicationViennent ensuite les parties qui facilitent vraiment la mise en place d'un APM. Il vous est demandé d'installer Nightwatch via Composer :

composer require laravel/nightwatch

On vous donne un ensemble de clés à placer dans votre fichier de variables d'environnement (il s'agit de clés factices, pour les amateurs de sécurité) :

Screenshot showing the agent setup promptEverything you need to know to get your agent reportingLa troisième étape empêche votre Account Nightwatch d'atteindre son quota au cours de la première heure : rappelez-vous que vous surveillez potentiellement des millions d'événements ici, Nightwatch a donc la possibilité de définir la taille de votre échantillon en tant que variable d'environnement :

Screenshot showing the agent sample size configurationHow much data do you want?Enfin, vous démarrez Nightwatch dans votre console locale, et celle-ci envoie la fréquence d'échantillonnage aux serveurs Nightwatch :

Screenshot of the panel showing you how to start the Nightwatch agent from the command lineRun your agent through php artisan, same as everything else

En quatre étapes seulement, vous disposez d'un APM. Exécutez votre Mock Node Webhook Server et les données arrivent :

Screenshot showing a populated Nightwatch dashboard monitoring your appElegant application monitoring is here!D'une manière ou d'une autre, j'ai même réussi à créer une requête malveillante qui a monopolisé le temps de l'unité centrale, ce qui se traduit par un pic dans le graphique de performance sur la droite. Vous disposez d'une tonne de fonctionnalités pour approfondir les données de votre application : violations SQL, exceptions de mémoire, tâches incomplètes, etc. Comme je générais des exceptions de manière aléatoire, j'ai dû regarder à quoi elles ressemblaient :

Screenshow showing exceptions rates on the Nightwatch dashboardWatch out for your exception ratesC'est une bonne chose. Il divise également les données entre celles qui sont gérées et celles qui ne le sont pas, ce qui vous permet d'avoir une meilleure vue d'ensemble des performances défensives du code de votre application.

Conclusion

Chaque année, les développeurs semblent être doués d'une autre partie de la pile d'applications Laravel qui n'a pas encore été couverte, et ce n'est pas une exception. Ce qui distingue Nightwatch, c'est que les fournisseurs tiers d'outils de surveillance et de performance des applications ne sont pas adaptés à Laravel, contrairement à ce qui se passe chez Laravel. À cet égard, pour les consommateurs de grandes quantités de données (l'utilisation de Verify, Messages ou Voice API a des systèmes de webhooks d'événements qui génèrent de grandes quantités de webhooks), il s'agit d'une victoire si facile qu'il est difficile de l'ignorer.

Vous avez une question ou souhaitez partager ce que vous construisez ?

Restez connecté et tenez-vous au courant des dernières nouvelles, astuces et événements concernant les développeurs.

Partager:

https://a.storyblok.com/f/270183/400x385/12b3020c69/james-seconde.png
James SecondeDéveloppeur PHP senior Advocate

Acteur de formation avec une thèse sur la comédie, je suis venu au développement PHP par le biais de la scène des rencontres. Vous pouvez me trouver en train de parler et d'écrire sur la technologie, ou de jouer/acheter des disques bizarres de ma collection de vinyles.