Webhooks
Recevez des notifications en temps réel pour les événements de messagerie en enregistrant des endpoints HTTPS. Base : https://sms.chinqit.com/api/v2.
Vue d'ensemble
Les webhooks vous permettent d'enregistrer des endpoints HTTPS publics sur lesquels ChinqIT pousse des notifications dès qu'un événement se produit. Chaque endpoint dispose de son propre secret de signature et peut s'abonner à n'importe quelle combinaison d'événements.
L'authentification et l'URL de base sont documentées dans la Référence API.
Catalogue des événements
Toutes les notifications utilisent l'enveloppe suivante :
{
"id": "evt_01j9z...",
"type": "message.sent",
"createdAt": "2026-06-27T10:00:00.000Z",
"data": { ... }
}| Événement | Déclencheur |
|---|---|
message.sent | SMS ou OTP-SMS accepté par l'opérateur (soumission réussie) |
message.rejected | SMS ou OTP-SMS reçu par l'opérateur mais refusé — facturé |
message.failed | SMS ou OTP-SMS que notre plateforme n'a pas pu soumettre à l'opérateur — non facturé |
bulk.completed | Tâche d'envoi en masse terminée, avec compteurs de succès/échec |
otp.verified | L'utilisateur final a saisi le code OTP correct |
Remarque : La confirmation de livraison finale n'est pas disponible (pas d'accusés de réception opérateur). Il n'existe donc pas d'événement
delivered. Les événementsmessage.sent,message.rejectedetmessage.failedreflètent uniquement le résultat de la soumission à l'opérateur.Remarque — rupture de compatibilité :
message.sentsignifie que l'opérateur a reçu le message et que vous avez été facturé.message.rejectedest émis lorsque l'opérateur le refuse après l'avoir reçu — généralement facturé, donc vérifiezdata.billed(il vautfalsedans le cas rare où ChinqIT bloque un numéro connu comme invalide avant soumission, qui n'atteint jamais l'opérateur).message.failedsignifie que le message n'a jamais atteint l'opérateur et qu'il n'a pas été facturé (ou a été remboursé s'il l'avait déjà été). Si votre intégration s'appuyait auparavant surmessage.failedpour détecter les refus, vous devez désormais également vous abonner àmessage.rejected— depuis ce changement, un refus n'émet plusmessage.failed.Remarque : Les destinataires d'un envoi en masse n'émettent pas d'événements par message. Seul
bulk.completedest émis à la fin du traitement.
message.sent
{
"id": "evt_01j9z...",
"type": "message.sent",
"createdAt": "2026-06-27T10:00:00.000Z",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+22238089336",
"senderId": "CHINQIT",
"channel": "sms",
"type": "message",
"submittedAt": "2026-06-27T10:00:00.000Z"
}
}message.rejected
{
"id": "evt_01j9z...",
"type": "message.rejected",
"createdAt": "2026-06-27T10:00:03.000Z",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+22238089336",
"senderId": "CHINQIT",
"channel": "sms",
"type": "message",
"billed": true
}
}data.billed fait foi : il vaut true lorsque l'opérateur a reçu et refusé le message (vous avez été facturé), et false dans le cas rare où ChinqIT bloque un numéro connu comme invalide avant soumission (jamais atteint l'opérateur, non facturé).
message.failed
{
"id": "evt_01j9z...",
"type": "message.failed",
"createdAt": "2026-06-27T10:00:05.000Z",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+22238089336",
"senderId": "CHINQIT",
"channel": "sms",
"type": "message",
"failureReason": "Insufficient balance"
}
}bulk.completed
{
"id": "evt_01j9z...",
"type": "bulk.completed",
"createdAt": "2026-06-27T10:05:00.000Z",
"data": {
"batchId": "batch_01j9z...",
"status": "completed",
"totalMessages": 1000,
"successfulCount": 985,
"failedCount": 12,
"invalidCount": 3,
"totalCost": 9.85
}
}otp.verified
{
"id": "evt_01j9z...",
"type": "otp.verified",
"createdAt": "2026-06-27T10:02:00.000Z",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+22238089336",
"verifiedAt": "2026-06-27T10:02:00.000Z"
}
}Sécurité
HTTPS obligatoire
Tous les endpoints webhook doivent utiliser HTTPS. Les URLs en HTTP sont rejetées.
Vérification de la signature X-Chinqit-Signature
Chaque livraison inclut les en-têtes suivants :
| En-tête | Description |
|---|---|
X-Chinqit-Signature | t=<unix>,v1=<hex> — horodatage et signature HMAC-SHA256 |
X-Chinqit-Timestamp | Horodatage Unix en secondes (même valeur que t= dans la signature) |
X-Chinqit-Delivery | Identifiant unique de la tentative de livraison (clé d'idempotence) |
Recette de vérification :
signature = HMAC_SHA256(secret, "${t}.${rawBody}")- Extrayez
tet toutes les valeursv1depuis l'en-têteX-Chinqit-Signature. - Calculez
HMAC_SHA256(secret, t + "." + rawBody)avec le corps brut (non parsé). - Comparez le résultat (en hex) à chaque valeur
v1. Acceptez si au moins une correspond. - Rejetez la requête si
X-Chinqit-Timestampest en dehors de votre fenêtre de tolérance (par exemple ±5 minutes) pour vous protéger contre les attaques par rejeu.
Exemple (Node.js) :
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySignature(secret, rawBody, sigHeader, tsHeader, toleranceSec = 300) {
const now = Math.floor(Date.now() / 1000);
const ts = parseInt(tsHeader, 10);
if (Math.abs(now - ts) > toleranceSec) return false;
const parts = sigHeader.split(',');
const t = parts.find(p => p.startsWith('t=')).slice(2);
const v1s = parts.filter(p => p.startsWith('v1=')).map(p => p.slice(3));
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return v1s.some(v => {
try { return timingSafeEqual(Buffer.from(v, 'hex'), Buffer.from(expected, 'hex')); }
catch { return false; }
});
}Idempotence
L'en-tête X-Chinqit-Delivery contient un identifiant stable de la livraison — la même valeur est réutilisée à chaque nouvelle tentative. Utilisez-le comme clé d'idempotence pour dédupliquer les événements reçus plusieurs fois.
Fiabilité
Nouvelles tentatives et backoff exponentiel
En cas d'échec (réponse non-2xx ou timeout), ChinqIT retente automatiquement la livraison jusqu'à 8 fois avec backoff exponentiel — les intervalles s'échelonnent progressivement (1 min, 5 min, 30 min, 2 h, 5 h, 10 h, jusqu'à 24 h entre deux tentatives), couvrant au total environ 1,5 à 2 jours.
Désactivation automatique
Un endpoint est automatiquement désactivé après 20 échecs consécutifs. Une alerte est envoyée à votre administrateur. Vous pouvez réactiver l'endpoint via l'API de gestion.
Rotation du secret avec fenêtre de chevauchement
Lors d'une rotation de secret (POST .../rotate-secret), l'ancien secret continue de signer les livraisons pendant une fenêtre de chevauchement de 24 heures. Pendant cette période, les livraisons incluent deux valeurs v1= dans l'en-tête de signature — l'une signée avec l'ancien secret, l'autre avec le nouveau. Acceptez si l'une ou l'autre est valide.
API de gestion
Toutes les routes nécessitent l'en-tête X-API-Key.
| Méthode | Endpoint | Description |
|---|---|---|
POST | /api/v2/webhooks | Créer un endpoint webhook |
GET | /api/v2/webhooks | Lister vos endpoints |
GET | /api/v2/webhooks/{id} | Obtenir un endpoint |
PATCH | /api/v2/webhooks/{id} | Mettre à jour un endpoint |
DELETE | /api/v2/webhooks/{id} | Supprimer un endpoint |
POST | /api/v2/webhooks/{id}/rotate-secret | Faire tourner le secret de signature |
POST | /api/v2/webhooks/{id}/reveal-secret | Révéler le secret de signature actuel (réservé au propriétaire, à tout moment) |
POST | /api/v2/webhooks/{id}/test | Envoyer une livraison de test |
GET | /api/v2/webhooks/{id}/deliveries | Consulter l'historique des livraisons |
Créer un endpoint (exemple) :
curl -X POST https://sms.chinqit.com/api/v2/webhooks \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/chinqit",
"events": ["message.sent", "message.failed", "otp.verified"]
}'Champs du corps de la requête :
url(requis) — l'endpoint HTTPS public vers lequel envoyer les événements.events(requis) — tableau des types d'événements auxquels s'abonner.apiKeyIds(optionnel) — restreint cet endpoint aux événements déclenchés par des clés API spécifiques (« applications »). Omettez ce champ ou passez[]pour recevoir les événements de toutes vos clés API. Lorsqu'il est renseigné, seuls les événements provenant des clés listées sont transmis à cet endpoint.
Réponse :
{
"success": true,
"webhook": {
"_id": "665f1a2b3c4d5e6f7a8b9c0d",
"userId": "6953a1b2c3d4e5f6a7b8c9d0",
"url": "https://example.com/webhooks/chinqit",
"events": ["message.sent", "message.failed", "otp.verified"],
"status": "enabled",
"secretPrefix": "whsec_ab12c…",
"consecutiveFailures": 0,
"createdAt": "2026-06-27T10:00:00.000Z",
"updatedAt": "2026-06-27T10:00:00.000Z"
},
"secret": "whsec_4f3c8e21a9b0c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2"
}Important : Le
secretcomplet (préfixewhsec_) n'est renvoyé qu'ici à la création, ainsi que par/rotate-secretet/reveal-secret. Toutes les autres réponses n'exposent quesecretPrefix. Conservez le secret complet immédiatement en lieu sûr.