Aller au contenu

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.

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}.

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 :

  1. 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).
  2. Rejetez la requête si |now − t| > 300 secondes (cela empêche les rejeux).
  3. Calculez le HMAC et comparez-le, avec une fonction à temps constant, à chaque v1 pré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 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.

  • 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 dans Xaalis-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.