Notifications (webhooks)
L'API prévient votre serveur dès qu'un paiement aboutit ou qu'un contrat est émis : inutile d'interroger l'API en boucle.
Configuration
Communiquez à COVERAF l'adresse de votre serveur qui recevra les notifications : une pour le test, une pour la production (https:// obligatoire, adresse publique). Vous recevez en retour un secret de notifications (cvr_whsec_...), différent de votre secret de signature, affiché une seule fois.
Événements
| type | Quand | donnees |
|---|---|---|
paiement.reussi | Mode B : le client a payé. | souscription_id, paiement_id, montant |
paiement.echoue | Mode B : le paiement a échoué (motif : solde_insuffisant, non_valide_par_le_client, erreur_operateur, refuse_par_operateur). La souscription reste payable. | souscription_id, paiement_id, motif |
contrat.emis | Un contrat est émis (modes A et B). Ses documents sont disponibles. | souscription_id, contrat_numero |
test.ping | Envoyé par POST /v1/webhooks/test. | message |
Chaque notification est une requête POST en JSON :
{
"id": "evt_5f1c2a9be04d77a3c1d2e8f0",
"objet": "evenement",
"type": "contrat.emis",
"environnement": "test",
"cree_le": "2026-11-04T17:35:56.305Z",
"donnees": {
"souscription_id": "sou_f6ac5791c1dd467dd238607f",
"contrat_numero": "TEST-CVR-API-MW2EE3CW-0845CBC0B0"
}
}| En-tête | Contenu |
|---|---|
X-Coveraf-Evenement | Le type d'événement. |
X-Coveraf-Timestamp | Heure d'envoi en secondes (horodatage Unix). |
X-Coveraf-Signature | v1= suivi du HMAC-SHA256 hexadécimal de <timestamp>.<corps brut>, avec votre secret de notifications. |
Vérifier une notification
Refusez toute notification dont la signature est fausse ou dont l'horodatage a plus de 5 minutes : elle ne vient pas de COVERAF.
import crypto from 'node:crypto'
import express from 'express'
const SECRET_NOTIFICATIONS = process.env.COVERAF_SECRET_NOTIFICATIONS // cvr_whsec_...
export function signatureValide(corpsBrut, timestamp, signature) {
if (!timestamp || !signature) return false
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false // 5 minutes
const attendue = 'v1=' + crypto.createHmac('sha256', SECRET_NOTIFICATIONS).update(`${timestamp}.${corpsBrut}`).digest('hex')
return attendue.length === signature.length && crypto.timingSafeEqual(Buffer.from(attendue), Buffer.from(signature))
}
const app = express()
// Corps BRUT : la signature porte sur les octets reçus, pas sur un JSON relu.
app.post('/coveraf/notifications', express.text({ type: 'application/json' }), (req, res) => {
if (!signatureValide(req.body, req.get('X-Coveraf-Timestamp'), req.get('X-Coveraf-Signature'))) {
return res.status(401).end()
}
const evenement = JSON.parse(req.body)
// Dédoublonnez avec evenement.id : un événement peut arriver plusieurs fois.
res.status(200).end() // répondez vite, traitez ensuite
traiter(evenement)
})Vérifiez la signature sur le corps brut, tel que reçu. Un JSON relu puis réécrit par votre framework n'a plus les mêmes octets, et la signature ne correspond plus.
Renvois et doublons
- Répondez par un code 2xx en moins de 10 secondes, puis traitez l'événement. Toute autre réponse, ou aucune, déclenche un nouvel envoi, après environ 1 min, 5 min, 30 min, 2 h, 6 h puis 12 h. Ensuite, l'envoi est abandonné.
- Un même événement peut donc arriver plusieurs fois : dédoublonnez avec son
id. - L'ordre d'arrivée n'est pas garanti. En cas de doute, l'état de référence est celui renvoyé par
GET /v1/souscriptions/{id}. - Les redirections ne sont pas suivies : donnez l'adresse finale.
Tester votre réception
Envoie immédiatement un événement test.ping à votre adresse et vous dit ce qu'elle a répondu (recu, statut_http, erreur). En sandbox, chaque paiement simulé déclenche aussi les vraies notifications.