Webhooks
Xaalis envoie un événement en POST à votre webhook_url chaque fois qu’un paiement ou un versement atteint un état
final. C’est ainsi que vous apprenez que l’argent est arrivé, et non par la redirection du client.
Définissez l’URL avec PATCH /v1/account {"webhook_url":"https://…"} (elle doit être en https://), ou demandez-le
à un opérateur.
Événements
Section intitulée « Événements »type |
Quand |
|---|---|
payment.succeeded |
le prestataire a confirmé ; net a été ajouté à available |
payment.failed |
refusé, ou annulé par le client |
payment.expired |
personne n’a payé dans les 30 minutes |
payout.succeeded |
l’argent est arrivé sur le portefeuille du bénéficiaire |
payout.failed |
le prestataire a refusé ; le montant et les frais sont revenus dans available |
payment.refunded |
un remboursement est arrivé chez le client |
invoice.created · invoice.paid · invoice.overdue |
factures d’abonnement — invoice.created contient le lien à envoyer |
subscription.past_due · subscription.canceled · subscription.completed |
changements d’état de l’abonnement ; expédiez les paiements en plusieurs fois sur completed |
{ "id": "whd_…", "type": "payment.succeeded", "livemode": true, "created_at": "2026-09-24T10:42:00.000Z", "data": { "object": { "id": "pay_…", "object": "payment", "amount": 15000, "fee": 225, "net": 14775, "status": "succeeded", "…": "…" } }}data.object est le même objet que celui renvoyé par GET /v1/payments/{id} ou GET /v1/payouts/{id}.
Vérifier la signature
Section intitulée « Vérifier la signature »Chaque requête comporte :
Xaalis-Signature: t=1758793320,v1=5f2b…Xaalis-Event-Id: whd_…v1 vaut HMAC-SHA256(webhook_secret, "<t>.<raw body>") en hexadécimal. Votre webhook_secret vous est remis une
seule fois, à la création du compte. Pour vérifier :
- Prenez les octets bruts de la requête, pas un objet JSON re-sérialisé (l’ordre des clés et les espaces changent la signature).
- Rejetez la requête si
|now − t| > 300secondes (cela empêche les rejeux). - Calculez le HMAC et comparez-le, avec une fonction à temps constant, à chaque
v1présent dans l’en-tête.
import { constructEvent } from "@xaalis/sdk";// express: app.post("/xaalis", express.raw({ type: "application/json" }), handler)const event = constructEvent(req.body, req.header("xaalis-signature"), process.env.XAALIS_WEBHOOK_SECRET!);Sans le SDK (Node) :
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(raw: string, header: string, secret: string): boolean { const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2))); const t = Number(parts.t); if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = Buffer.from(createHmac("sha256", secret).update(`${t}.${raw}`).digest("hex")); const got = Buffer.from(parts.v1 ?? ""); return got.length === expected.length && timingSafeEqual(got, expected);}Répondez vite, traitez ensuite
Section intitulée « Répondez vite, traitez ensuite »Répondez par un 2xx en moins de 10 secondes, puis faites le traitement. Tout le reste — un délai dépassé, un 3xx
(les redirections ne sont pas suivies), un 4xx, un 5xx — compte comme un échec et donne lieu à une nouvelle
tentative.
Nouvelles tentatives et doublons
Section intitulée « Nouvelles tentatives et doublons »- Jusqu’à 12 tentatives, avec un délai exponentiel qui commence à 10 s (10 s, 20 s, 40 s… environ 5,7 heures au
total). Au-delà, l’envoi est marqué
failed. - La livraison est garantie au moins une fois : le même événement peut arriver deux fois. Dédoublonnez sur
id(aussi présent dansXaalis-Event-Id), par exemple avec une colonne unique. - Les événements peuvent arriver dans le désordre. Si l’ordre compte, relisez l’objet avec
GET /v1/payments/{id}. - Vérifiez
livemode: les événements de test et de production arrivent sur la même URL.
GET /v1/webhook-deliveries?limit=20 liste les envois récents avec status (pending, succeeded, failed),
attempts, last_status_code et last_error, pour déboguer votre point de terminaison.