Prendre RDVContact
Retour à toutes les notes
1 juin 2026

Bâtir des webhooks résilients pour les infrastructures serverless

Stripe/ProviderPOST /webhooksSQS Queue15 RetriesLambda / HandlerStatus: 200 OK502

Bâtir des webhooks résilients pour les infrastructures serverless

Bâtir des webhooks résilients pour les infrastructures serverless n'est pas une option ; c'est une exigence absolue. Si votre système reçoit des webhooks entrants de fournisseurs tiers (Stripe, GitHub, Twilio) et que vous comptez sur un traitement synchrone et immédiat, votre architecture est déjà défaillante. Dans un environnement serverless, les limites de concurrence, les démarrages à froid (cold starts) et les pannes d'API en aval font du traitement synchrone des webhooks une recette idéale pour perdre des événements et obtenir des états incohérents.

Dans cet article, je vais vous montrer précisément comment concevoir, développer et déployer un pipeline d'ingestion de webhooks asynchrones qui garantit une livraison au moins une fois (at-least-once), gère d'importants pics de trafic sans perdre de données et se rétablit sans encombre en cas de pannes des services en aval.

Le problème : L'échec de l'ingestion synchrone

La plupart des ingénieurs conçoivent des points d'accès de webhooks de la manière suivante : une requête POST arrive sur API Gateway, déclenche une fonction AWS Lambda, qui valide ensuite la charge utile (payload), interroge une base de données, appelle une API externe, et retourne enfin un code 200 OK.

Cela fonctionne en développement. En production, le système s'effondre rapidement.

Pourquoi ?

  1. Délais d'attente (Timeouts) : API Gateway a une limite stricte de délai d'attente de 29 secondes. Si votre base de données en aval est ralentie ou si l'API externe que vous appelez ne répond pas, l'exécution de la fonction Lambda dépassera cette limite. Le fournisseur tiers verra un échec et réessaiera de manière agressive (créant un effet de thundering herd ou troupeau rugissant) ou abandonnera complètement l'envoi.
  2. Limites de concurrence : Les fonctions serverless s'adaptent rapidement à la charge, mais elles ne sont pas à l'abri des limites de concurrence. Si vous recevez soudainement une salve de 10 000 webhooks, vous risquez de saturer la limite d'exécutions simultanées de votre compte, incitant API Gateway à retourner des erreurs 429 Too Many Requests. Certains fournisseurs retenteront l'envoi après une erreur 429, mais pas tous.
  3. Pannes partielles : Si votre fonction Lambda s'interrompt au milieu de son traitement - par exemple, après avoir mis à jour la base de données mais avant d'avoir envoyé l'e-mail de confirmation - vous vous retrouvez avec un état de données incohérent.

Pourquoi c'est difficile

La gestion des webhooks est intrinsèquement complexe car vous ne contrôlez pas le débit des requêtes entrantes. C'est l'émetteur qui décide quand et à quelle vitesse envoyer les données. Si votre système ne peut pas absorber ce flux, il se brise.

Le serverless accentue ce problème. Bien que les plateformes serverless proposent une mise à l'échelle automatique, les ressources auxquelles elles se connectent (bases de données RDS, API tierces) n'en disposent généralement pas. Vous vous retrouvez avec une couche de calcul hautement évolutive qui sature une couche de stockage fragile.

Pour résoudre ce problème, vous devez découpler votre architecture. L'ingestion du webhook doit être totalement séparée de son traitement.

Architecture : Le pipeline d'ingestion

La seule manière correcte de gérer les webhooks en serverless est de passer par une ingestion asynchrone. L'architecture se présente ainsi :

  1. Couche d'ingestion : API Gateway reçoit la requête POST.
  2. Couche de mise en file d'attente : API Gateway pousse directement la charge utile dans une file d'attente Amazon SQS. Aucune fonction Lambda n'intervient à ce stade.
  3. Couche de traitement : Un mappage de source d'événements déclenche une fonction Lambda pour traiter les messages de la file SQS avec une concurrence maîtrisée.
  4. File d'attente des messages d'erreur (Dead Letter Queue - DLQ) : Si un message échoue à plusieurs reprises lors du traitement, il est déplacé vers une DLQ pour une inspection manuelle.

Ce modèle est indispensable pour les systèmes à fort trafic. En connectant directement API Gateway à SQS, vous éliminez la Lambda d'ingestion, ce qui réduit les coûts et supprime un point de défaillance potentiel. API Gateway retournera un code 200 OK en quelques millisecondes, garantissant au fournisseur du webhook que la livraison a réussi.

Implémentation : AWS CDK (v2.100.0)

Développons ce système en utilisant AWS CDK (TypeScript). Nous allons définir la file d'attente SQS, la file DLQ, la Lambda de traitement et l'intégration directe avec API Gateway.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
import * as iam from 'aws-cdk-lib/aws-iam';

export class WebhookIngestionStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. Créer la Dead Letter Queue (File d'attente des messages d'erreur)
    const webhookDlq = new sqs.Queue(this, 'WebhookDlq', {
      retentionPeriod: cdk.Duration.days(14),
    });

    // 2. Créer la file d'attente d'ingestion principale
    const webhookQueue = new sqs.Queue(this, 'WebhookQueue', {
      visibilityTimeout: cdk.Duration.seconds(30),
      deadLetterQueue: {
        queue: webhookDlq,
        maxReceiveCount: 3,
      },
    });

    // 3. Créer la Lambda de traitement
    const processorLambda = new lambda.Function(this, 'ProcessorLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/processor'),
      timeout: cdk.Duration.seconds(10),
    });

    // 4. Associer SQS à la Lambda avec une concurrence contrôlée
    processorLambda.addEventSource(
      new lambdaEventSources.SqsEventSource(webhookQueue, {
        batchSize: 10,
        maxConcurrency: 5, // Évite de surcharger les services en aval
      })
    );

    // 5. Intégration directe de API Gateway vers SQS
    const api = new apigateway.RestApi(this, 'WebhookApi', {
      restApiName: 'Webhook Ingestion Service',
    });

    const integrationRole = new iam.Role(this, 'ApiGatewaySqsRole', {
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
    });
    webhookQueue.grantSendMessages(integrationRole);

    const sqsIntegration = new apigateway.AwsIntegration({
      service: 'sqs',
      path: `${cdk.Aws.ACCOUNT_ID}/${webhookQueue.queueName}`,
      integrationHttpMethod: 'POST',
      options: {
        credentialsRole: integrationRole,
        passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
        requestParameters: {
          'integration.request.header.Content-Type': `'application/x-www-form-urlencoded'`,
        },
        requestTemplates: {
          'application/json': 'Action=SendMessage&MessageBody=$util.urlEncode($input.body)',
        },
        integrationResponses: [
          {
            statusCode: '200',
            responseTemplates: {
              'application/json': '{"status":"received"}',
            },
          },
        ],
      },
    });

    api.root.addResource('webhooks').addMethod('POST', sqsIntegration, {
      methodResponses: [{ statusCode: '200' }],
    });
  }
}

Logique de traitement (Node.js 18.x)

Votre fonction Lambda récupère désormais les messages de SQS. Comme nous avons configuré batchSize: 10, vous devez gérer les échecs partiels de lots. Si un seul message sur les 10 échoue et que vous levez une erreur, SQS renverra l'intégralité des 10 messages pour une nouvelle tentative. C'est inefficace et risqué.

Au lieu de cela, retournez les identifiants spécifiques des messages ayant échoué pour que SQS ne tente de renvoyer que ceux-là.

// lambda/processor/index.js

exports.handler = async (event) => {
  const batchItemFailures = [];

  for (const record of event.Records) {
    try {
      const payload = JSON.parse(record.body);
      
      // Implémentez la vérification d'idempotence ici !
      // await checkIdempotency(payload.id);
      
      console.log('Traitement du webhook :', payload);
      
      // Simuler l'écriture en base de données ou un appel d'API externe
      // await processWebhook(payload);

    } catch (error) {
      console.error(`Échec du traitement du message ${record.messageId}`, error);
      batchItemFailures.push({ itemIdentifier: record.messageId });
    }
  }

  return { batchItemFailures };
};

Pièges à éviter

Cette architecture est largement supérieure à une ingestion synchrone, mais elle apporte de nouveaux défis.

  1. Idempotence : SQS garantit une livraison au moins une fois, ce qui signifie que votre Lambda finira par recevoir le même webhook plusieurs fois. Si vous traitez deux fois un paiement Stripe, vos clients seront mécontents. Vous devez stocker l'identifiant du webhook dans un espace de stockage rapide (comme DynamoDB) et vérifier s'il a déjà été traité avant d'exécuter votre logique.
  2. Ordre des événements : Les files d'attente SQS standards ne garantissent pas l'ordre de réception des messages. Si un utilisateur met à jour son profil deux fois en une seconde, la deuxième mise à jour peut être traitée avant la première. Si l'ordre est critique, utilisez une file d'attente SQS FIFO. Notez cependant que les files FIFO ont des limites de débit plus basses et nécessitent une configuration minutieuse de l'attribut MessageGroupId. Dans la plupart des cas, des files d'attente standards combinées à une vérification de l'horodatage « dernière mise à jour » dans votre base de données suffisent.
  3. Limites de taille de la charge utile : SQS limite la taille maximale de la charge utile à 256 Ko. La plupart des webhooks sont plus petits, mais si vous attendez des objets JSON volumineux, vous devrez intercepter la charge utile, la stocker dans S3, et transmettre sa clé d'accès S3 à SQS.

Résultat

En implémentant ce pipeline d'ingestion, vous transformez un point d'accès synchrone et fragile en un moteur de traitement durable capable de gérer de fortes concurrences.

API Gateway encaisse le flux initial en répondant aux fournisseurs en quelques millisecondes. SQS fait office d'amortisseur en stockant les charges utiles. Vos fonctions Lambda traitent la file d'attente à un rythme adapté, évitant de surcharger vos bases de données relationnelles. Et en cas d'échec inévitable, la Dead Letter Queue recueille les erreurs, garantissant qu'aucune donnée ne soit perdue.

Bâtir des webhooks résilients pour les infrastructures serverless demande plus de travail de conception initial, mais la tranquillité opérationnelle obtenue justifie amplement chaque ligne de code écrite. Arrêtez de concevoir des webhooks synchrones. Commencez à découpler l'ingestion du traitement.

Service Seven Labs

Intégration d'Automations IA & Workflows

Nous construisons des systèmes d'automation. Voir nos services d'automation →
Loading...

Lire la suite

The AI Engineer Shortage and How to Outsource Smartly

The AI engineer shortage is crippling ambitious roadmaps. Here is exactly how to outsource smartly, ...

Lire l'article

Moving Beyond Chat: The Architecture of Multi-Agent Systems

Single-prompt LLM applications are fragile. Discover how multi-agent orchestration frameworks are br...

Lire l'article
Chat with us