Webhooks

Recevoir les événements Paylet sur votre serveur : s'abonner aux événements, comprendre leurs charges utiles et vérifier la signature HMAC de chaque envoi.

Prérequis

  • Un compte marchand Paylet.
  • Un endpoint HTTPS public capable de recevoir des POST JSON.
  • Un jeton Bearer (connexion dashboard) pour gérer les abonnements.

S'abonner aux événements

Un abonnement webhook est un endpoint (URL) associé à une liste d'événements. Créez-le avec votre jeton Bearer :

curlPOST /v1/webhook-endpoints
curl -X POST https://api.paylet.fr/v1/webhook-endpoints \
  -H "Authorization: Bearer $PAYLET_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://exemple.com/webhooks/paylet",
    "events": ["transaction.settled", "topup.completed"]
  }'

Réponse 201 :

jsonAbonnement créé
{
  "webhook_endpoint": {
    "id": "<uuid>",
    "url": "https://exemple.com/webhooks/paylet",
    "events": ["transaction.settled", "topup.completed"],
    "secret": "whsec_<secret-hmac>",
    "status": "active",
    "created_at": "2026-08-21T12:00:00Z"
  }
}

Le secret n'est renvoyé qu'une fois

Le champ secret sert à vérifier la signature des envois. Conservez-le précieusement : il n'est renvoyé qu'à la création.

Pour lister ou supprimer un abonnement :

curl https://api.paylet.fr/v1/webhook-endpoints \
  -H "Authorization: Bearer $PAYLET_JWT"

Événements disponibles

ÉvénementDéclencheurChamps clés de data
transaction.settledUn paiement M2M a été réglé.transaction_id, amount_minor, status, settled_at
topup.completedUn rechargement Stripe a crédité le compte.topup_id, amount_minor, currency
balance.updatedLe solde a bougé (optionnel, sur abonnement).solde courant après mouvement

Exemples de charges utiles

jsontransaction.settled
{
  "event": "transaction.settled",
  "data": {
    "transaction_id": "<uuid>",
    "amount_minor": 1500000,
    "currency": "EUR",
    "status": "settled",
    "settled_at": "2026-08-21T12:00:00Z"
  }
}
jsontopup.completed
{
  "event": "topup.completed",
  "data": {
    "topup_id": "<uuid>",
    "amount_minor": 1000000,
    "currency": "EUR"
  }
}

Vérifier la signature HMAC

Chaque envoi est signé : Paylet ajoute l'en-tête X-Webhook-Signature contenant le HMAC-SHA256 du corps brut (octets exacts, non reformatés), calculé avec votre secret. Vérifiez toujours cette signature avant de traiter l'événement.

FastAPI
import hashlib
import hmac
import os
from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["PAYLET_WEBHOOK_SECRET"].encode("utf-8")

@app.post("/webhooks/paylet")
async def paylet_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")

    # HMAC-SHA256 du corps brut, comparé en temps constant.
    expected = hmac.new(WEBHOOK_SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        raise HTTPException(status_code=400, detail="signature invalide")

    payload = await request.json()
    print(payload["event"], payload["data"])
    return {"received": True}

Précision sur l'encodage

L'en-tête transporte la signature en hexadécimal, convention identique à celle des primitives de signature du SDK (sign_payload / signPayload). Utilisez le même encodage des deux côtés pour que les signatures coïncident.

Erreurs courantes

codeHTTPSignification
invalid_request400URL non HTTPS en production, ou corps malformé.
authentication_required401Jeton Bearer absent ou expiré.
not_found404Endpoint webhook inexistant.