Appearance
Webhooks
Epayment appelle votre endpoint HTTPS en POST JSON signé HMAC.
Événements checkout
| Événement | Quand |
|---|---|
checkout.completed | Paiement confirmé (MM, carte ou espèces agence) |
checkout.failed | Session clôturée après échec collect |
checkout.cancelled | Session annulée (ministry ou citoyen) |
checkout.expired | TTL session sans paiement |
platform.webhook.ping | Test manuel portail régie |
Idempotence côté client : (session_id, event) ou x-epayment-event-id.
Champs communs sur tous les payloads checkout : event, occurred_at, session_id, tenant_id, application_id, order_id, notice_id, paid, metadata optionnel, service_code / service_label si catalogue lié.
checkout.completed
Paiement constaté. paid est toujours true.
json
{
"event": "checkout.completed",
"occurred_at": "2026-08-20T14:32:01.000Z",
"session_id": "chs_01JXXXXXXXX",
"tenant_id": "11111111-1111-4111-8111-111111111111",
"application_id": "22222222-2222-4222-8222-222222222222",
"order_id": "CJ-2026-88421",
"notice_id": "44444444-4444-4444-8444-444444444444",
"paid": true,
"metadata": { "tribunal": "Brazzaville" },
"service_code": "casier_judiciaire",
"service_label": "Casier judiciaire"
}Sur paid: true et order_id connu : débloquer le dossier (idempotent). Le canal PSP ou connecteur n'apparaît pas dans le webhook ministry.
Champ optionnel revenue_shares
Lorsque des clés de répartition étaient actives au moment du collect, le payload peut inclure un tableau revenue_shares : montants principaux alloués par bénéficiaire (codes, poids, mode). Information seulement : ce n'est pas un ordre de virement ni une instruction de remise ; les versements aux tiers restent exécutés hors plateforme. Ne pas utiliser ce champ pour déclencher des paiements automatiques.
checkout.failed
Collect échoué (refus PSP, timeout approbation, abandon). paid est false. failure_code présent.
json
{
"event": "checkout.failed",
"occurred_at": "2026-08-20T14:45:12.000Z",
"session_id": "chs_01JXXXXXXXX",
"tenant_id": "11111111-1111-4111-8111-111111111111",
"application_id": "22222222-2222-4222-8222-222222222222",
"order_id": "CJ-2026-88421",
"notice_id": "44444444-4444-4444-8444-444444444444",
"paid": false,
"failure_code": "collect.rejected",
"metadata": { "tribunal": "Brazzaville" },
"service_code": "casier_judiciaire",
"service_label": "Casier judiciaire"
}Codes failure_code courants : collect.rejected, collect.approval_timeout, collect.abandoned. Pas de nom de connecteur dans le payload.
checkout.cancelled
Session annulée explicitement. paid est false. Pas de failure_code.
json
{
"event": "checkout.cancelled",
"occurred_at": "2026-08-20T14:20:00.000Z",
"session_id": "chs_01JXXXXXXXX",
"tenant_id": "11111111-1111-4111-8111-111111111111",
"application_id": "22222222-2222-4222-8222-222222222222",
"order_id": "CJ-2026-88421",
"notice_id": "44444444-4444-4444-8444-444444444444",
"paid": false,
"metadata": { "tribunal": "Brazzaville" },
"service_code": "casier_judiciaire",
"service_label": "Casier judiciaire"
}checkout.expired
TTL session écoulé sans paiement. paid est false. Pas de failure_code.
json
{
"event": "checkout.expired",
"occurred_at": "2026-08-20T15:00:00.000Z",
"session_id": "chs_01JXXXXXXXX",
"tenant_id": "11111111-1111-4111-8111-111111111111",
"application_id": "22222222-2222-4222-8222-222222222222",
"order_id": "CJ-2026-88421",
"notice_id": "44444444-4444-4444-8444-444444444444",
"paid": false,
"metadata": { "tribunal": "Brazzaville" },
"service_code": "casier_judiciaire",
"service_label": "Casier judiciaire"
}Headers
| Header | Description |
|---|---|
Content-Type | application/json |
x-epayment-timestamp | Epoch Unix (secondes) |
x-epayment-event-id | UUID livraison (idempotence) |
x-epayment-signature | sha256={hex} |
x-epayment-signature-previous | Pendant 24 h après rotation secret |
Vérification signature
- Lire
rawBodyavant parsing JSON. signed_payload = "{timestamp}.{rawBody}"HMAC-SHA256(webhook_secret, signed_payload)en hex.- Comparer à
x-epayment-signature(retirer préfixesha256=). - Si
signature-previousprésent, accepter aussi l'ancien secret (rotation).
javascript
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyEpaymentWebhook(rawBody, headers, secret) {
const timestamp = headers['x-epayment-timestamp'];
const signature = headers['x-epayment-signature'];
if (!timestamp || !signature?.startsWith('sha256=')) return false;
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`, 'utf8')
.digest('hex');
const received = signature.slice('sha256='.length);
return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}Réponse attendue
HTTP 2xx dans 10 secondes. Idempotence : même x-epayment-event-id peut revenir (retry). 5xx seulement pour pannes temporaires réelles.
Retries : jusqu'à 5 tentatives (1s, 5s, 25s, 125s).
Rotation secret
- Officer lance rotation sur l'endpoint (
POST .../rotate-secret). - Nouveau secret remis une fois.
- ~24 h : les deux signatures valides.
- Mettre à jour coffre, retirer ancien secret.
Configuration
Endpoints webhook : portail régie ou super-admin. Pas d'API self-service ministry en onboarding standard.
Zéro endpoints actifs = aucune livraison (checkout continue normalement).
Complément embed
En mode embed, webhook reste le filet principal. verify-completion permet une UX synchrone ; les deux doivent appeler la même logique métier idempotente sur order_id.
Index payloads : payloads-index.