Aller au contenu
Support

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

typeQuanddonnees
paiement.reussiMode B : le client a payé.souscription_id, paiement_id, montant
paiement.echoueMode 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.emisUn contrat est émis (modes A et B). Ses documents sont disponibles.souscription_id, contrat_numero
test.pingEnvoyé par POST /v1/webhooks/test.message

Chaque notification est une requête POST en JSON :

Corps reçu
{
  "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êteContenu
X-Coveraf-EvenementLe type d'événement.
X-Coveraf-TimestampHeure d'envoi en secondes (horodatage Unix).
X-Coveraf-Signaturev1= 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)
})
Le piège classique

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

POSThttps://api.coveraf.com/v1/webhooks/test

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.