Skip to content

Webhooks ​

Epayment appelle votre endpoint HTTPS en POST JSON signé HMAC.

Événements checkout ​

ÉvénementQuand
checkout.completedPaiement confirmé (MM, carte ou espèces agence)
checkout.failedSession clôturée après échec collect
checkout.cancelledSession annulée (ministry ou citoyen)
checkout.expiredTTL session sans paiement
platform.webhook.pingTest 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 ​

HeaderDescription
Content-Typeapplication/json
x-epayment-timestampEpoch Unix (secondes)
x-epayment-event-idUUID livraison (idempotence)
x-epayment-signaturesha256={hex}
x-epayment-signature-previousPendant 24 h après rotation secret

Vérification signature ​

  1. Lire rawBody avant parsing JSON.
  2. signed_payload = "{timestamp}.{rawBody}"
  3. HMAC-SHA256(webhook_secret, signed_payload) en hex.
  4. Comparer à x-epayment-signature (retirer préfixe sha256=).
  5. Si signature-previous pré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 ​

  1. Officer lance rotation sur l'endpoint (POST .../rotate-secret).
  2. Nouveau secret remis une fois.
  3. ~24 h : les deux signatures valides.
  4. 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.

Plateforme d'orchestration de paiement DPI. Les fonds ne transitent pas par Epayment.