Webhooks

Créez un endpoint, vérifiez la signature et traitez les événements de livraison avec l’API REST. Le SDK npm public n’est pas encore disponible.

Signature

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 message.delivered et message.failed dans votre système métier.

POST /v1/webhooks

  • message.queued
  • message.sent
  • message.delivered
  • message.failed
javascript
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}`);
console.log(payload.signingSecret);

En-têtes

HeaderValeur
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

json
{
  "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

javascript
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);
}