Retour au quickstartWebhook basics

Recevoir et vérifier les événements

Les webhooks transmettent les changements d’état reçus par la plateforme. Ce guide décrit la création d’un endpoint, la signature et le payload avec l’API REST et les primitives cryptographiques de Node.js. Le SDK npm public n’est pas encore disponible.

Règles de base

Les callbacks utilisent une signature HMAC-SHA256 sur `timestamp.payload`. Le timestamp et la signature arrivent dans les headers HTTP.

Retourner un 2xx rapidement quand le callback est traité.
Traiter `x-coussema-event` comme la source de vérité pour le type d’événement.
Vérifier `x-coussema-signature` avec le secret `signingSecret` renvoyé à la création.
Enregistrer les événements SMS et `whatsapp.message.*` utiles dans votre système métier.
Pour souscrire à `whatsapp.message.*`, la clé doit avoir `webhooks:write` et `whatsapp:read`.
Endpoint recommandé

POST /v1/webhooks

const apiKey = process.env.COUSSEMA_API_KEY ?? '';
const response = await fetch('https://api.coussema.com/v1/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com/webhooks/coussema',
    subscribed_events: ['message.delivered', 'message.failed'],
  }),
});

const payload = await response.json();
if (!response.ok) throw new Error(`Webhook request failed: ${response.status}`);
// Stockez payload.signingSecret immédiatement dans un coffre serveur.
// Ne le journalisez jamais et ne l’envoyez jamais au navigateur.
eventmessage.queued
eventmessage.sent
eventmessage.delivered
eventmessage.failed
eventmessage.delivery_unconfirmed
eventwhatsapp.message.sent
eventwhatsapp.message.delivered
eventwhatsapp.message.read
eventwhatsapp.message.failed
eventwhatsapp.message.received

Headers de callback

La plateforme livre les callbacks avec les headers suivants. Le `x-coussema-delivery-id` vous aide à faire de l’idempotence côté receveur.

content-typeapplication/json
user-agentCoussema-Webhooks/1.0
x-coussema-delivery-iddelivery UUID
x-coussema-eventmessage.delivered
x-coussema-timestamp2026-04-09T10:15:30.000Z
x-coussema-signaturev1=<hmac-sha256-hex>

Payload livré

Le corps du callback contient l’organisation et l’objet message normalisé.

{
  "organization_id": "2f0dd8f4-7b1c-4f38-b4c1-f4bc8aa6e8f4",
  "message": {
    "id": "a2f7e5cb-bbe9-4d7d-bd17-80d1d7a5e5af",
    "campaign_id": null,
    "recipient_phone": "+243810000000",
    "sender_name": "Coussema",
    "status": "delivered",
    "source_surface": "api",
    "sent_at": "2026-04-09T10:11:11.000Z",
    "failed_at": null,
    "delivered_at": "2026-04-09T10:13:45.000Z",
    "failure": null
  }
}

Vérification Node.js

Le worker de la plateforme retry les callbacks qui ne retournent pas de 2xx. Vérifiez d’abord la signature, puis dédupliquez et persistez l’événement.

import { createHmac, timingSafeEqual } from 'node:crypto';

function handleWebhook(rawBody, headers) {
  const timestamp = headers.get('x-coussema-timestamp');
  const signature = headers.get('x-coussema-signature');
  const secret = process.env.COUSSEMA_WEBHOOK_SECRET ?? '';
  if (!timestamp || !signature || !secret) throw new Error('Missing signature data.');

  const [version, incomingDigest = ''] = signature.split('=');
  const timestampMs = Date.parse(timestamp);
  const timestampIsFresh = Number.isFinite(timestampMs)
    && Math.abs(Date.now() - timestampMs) <= 5 * 60 * 1000;
  const expectedDigest = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8')
    .digest('hex');
  const valid = timestampIsFresh
    && version === 'v1'
    && incomingDigest.length === expectedDigest.length
    && timingSafeEqual(Buffer.from(incomingDigest, 'utf8'), Buffer.from(expectedDigest, 'utf8'));

  if (!valid) throw new Error('Invalid Coussema webhook signature.');
  return JSON.parse(rawBody);
}
  • Acceptez et dédupliquez par `x-coussema-delivery-id`.
  • Répondez en 2xx une fois le callback enregistré dans votre système.
  • Utilisez le secret retourné à la création pour vérifier la signature.
  • Traitez `message.delivered` et `message.failed` comme les états métier principaux.