Webhooks sortants
Événements poussés par l'agrégateur vers l'URL webhook du partenaire : succès et échec d'émission.
Vue d'ensemble
Après un paiement confirmé, l'agrégateur déclenche l'émission du contrat
ASKIA puis notifie le partenaire via un POST HTTP signé vers l'URL webhook
configurée (configuration).
Le webhook est le mecanisme principal de notification asynchrone. La relecture
de reference via GET /partner/contracts/requests/{reference} sert surtout de
fallback ou de reconciliation.
Webhook entrant paiement (contexte)
Avant d'émettre le webhook sortant, l'agrégateur reçoit lui-même un webhook entrant de la passerelle de paiement :
method : string (default: POST)
POST
path : string
/api/v1/webhooks/dexpay
| Header | Description |
|---|---|
x-webhook-signature | HMAC SHA256 du body, clé interne de signature paiement. |
Content-Type | application/json. |
Cet endpoint est appelé uniquement par la passerelle de paiement. Il n'a pas à être appelé par les partenaires. Il est documenté ici pour clarifier l'origine du déclenchement de l'émission.
Événements sortants
Deux événements sont émis vers le partenaire :
| Événement | Quand ? |
|---|---|
insurance.contract.issued | Paiement OK et contrat ASKIA émis avec succès. |
insurance.contract.issuance_failed | Paiement OK mais émission ASKIA en échec. |
Aucun événement n'est émis pour un paiement qui échoue avant l'étape d'émission
(annulation, échec, etc.). Utilisez
GET /api/v1/partner/contracts/requests/:reference pour suivre ces cas.
Headers envoyés au partenaire
| Header | Description |
|---|---|
Content-Type | application/json. |
x-insurance-event | Nom de l'événement (voir table ci-dessus). |
x-insurance-reference | reference corrélant le flow (identique à la création de session). |
x-insurance-signature | HMAC SHA256 hex du body brut avec le webhookSecret (si secret configuré). |
Réponse attendue du partenaire
- Retourner
2xxrapidement si le message est bien recu. - Rejeter avec
401si la signature est invalide. - Eviter tout traitement long avant la reponse HTTP; deferer le reste dans une file.
Payload — insurance.contract.issued
{
"event": "insurance.contract.issued",
"reference": "INS-AGG-2026-000123",
"paymentStatus": "COMPLETED",
"issuedAt": "2026-04-24T19:00:00.000Z",
"data": {
"contractId": "680a67c6d46c0b8dd2a02f2f",
"numeroPolice": "8400550AS000033",
"numeroFacture": "8400/2026/000006",
"status": "ACTIVE",
"liens": {
"linkAttestation": "https://aas.diotali.com/#/attestation/SN003KC3TKA",
"linkCarteBrune": "https://aas.diotali.com/#/carte-brune/SN003KC3TKA"
}
}
}Payload — insurance.contract.issuance_failed
{
"event": "insurance.contract.issuance_failed",
"reference": "INS-AGG-2026-000123",
"paymentStatus": "COMPLETED",
"issuedAt": "2026-04-24T19:01:00.000Z",
"data": {
"issuanceStatus": "FAILED",
"error": "Erreur émission contrat après paiement"
}
}issuance_failed signifie que l'utilisateur a payé mais n'a pas de contrat.
C'est un cas à remonter immédiatement côté partenaire (alerting, support).
Voir Erreurs et troubleshooting.
Vérification de signature
Le header x-insurance-signature est construit ainsi :
x-insurance-signature: <hex(HMAC_SHA256(webhookSecret, raw_body))>Quelques règles impératives :
- La signature est calculée sur le body brut (bytes exacts reçus), pas sur un JSON re-sérialisé.
- Comparez avec une fonction à temps constant (
crypto.timingSafeEqual,hmac.compare_digest, etc.) pour éviter les timing attacks. - Si
webhookSecretConfigured = false, aucun header n'est envoyé — vous devez alors vous reposer sur un mécanisme alternatif (IP allowlist, mTLS). Non recommandé en production.
Exemple Node.js
import crypto from 'node:crypto';
export function isValidSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader) return false;
const received = Buffer.from(signatureHeader, 'hex');
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return received.length === expected.length
&& crypto.timingSafeEqual(received, expected);
}Exemple Python
import hmac, hashlib
def is_valid_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header:
return False
received = bytes.fromhex(signature_header)
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
return hmac.compare_digest(received, expected)Retry
La livraison webhook est effectuée en best-effort. Une politique de retry automatique est prévue côté plateforme, mais n'est pas contractualisée dans cette version.
Transition d etat recommandee cote partenaire
| Signal recu | Etat interne conseille |
|---|---|
insurance.contract.issued | contrat actif / dossier termine |
insurance.contract.issuance_failed | dossier en incident support |
Pas de webhook mais ACTIVE via relecture | contrat actif, relecture de reconciliation |
Pas de webhook et PAYMENT_FAILED | paiement echoue ou annule |
Recommandations côté partenaire
Idempotence
Utilisez x-insurance-reference comme clé unique côté base de données.
Si vous recevez deux fois la même référence :
- soit le handler a déjà tourné → ignorer et répondre
200 OK, - soit il s'agit d'une mise à jour (ex: passage de
issuance_failedàissuedsuite à un rejeu) → mettre à jour l'état interne.
Anti-replay
Stocker la paire (reference, event, received_at) avec un TTL raisonnable
(30 jours). Rejeter tout événement identique déjà traité.
Timeouts et performance
- Répondre
2xxen moins de 5 secondes. - Déporter le traitement lourd (envoi email, génération PDF, etc.) dans une file de jobs.
- Ne pas vérifier la signature après avoir parsé le JSON : parser en premier peut ouvrir une surface d'attaque (JSON bomb). Vérifiez la signature sur le body brut, puis parsez.
Recette intégration
| Test | Attendu |
|---|---|
| Webhook avec signature valide | Handler exécuté, 200 retourné. |
| Webhook avec signature invalide | 401 retourné, aucun traitement. |
| Webhook sans header signature (et secret configuré côté agg) | 401 retourné. |
Même reference reçue 3 fois | Traitement une seule fois côté partenaire. |
Webhook issuance_failed puis issued (rejeu admin) | État passe correctement à issued sans duplication. |
Comprendre le parcours complet de la demande au contrat actif.
Tester les endpoints et consulter les schemas OpenAPI reels.
Verifier la signature et traiter les emissions en temps reel.
Valider les prerequis de production avant ouverture partenaire.