Skip to content

Webhooks

Les webhooks sont des charges utiles d'événements signées HMAC-SHA256 livrées à votre endpoint quand quelque chose se passe sur un paiement. Ils ré-essayent en cas d'échec et constituent la confirmation autoritative qu'un paiement a été capturé.

Événements

Événements de paiement

ÉvénementQuand
payment.completedVente capturée avec succès (état final du flux Sale)
payment.capturedAutorisation passée en capturée (flux Auth → Capture séparé)
payment.capture_failedTentative de capture sur un paiement autorisé échouée
payment.failedPaiement refusé ou échoué
payment.refundedRemboursement traité (total ou partiel)
payment.refund_failedTentative de remboursement refusée ou en erreur — la capture d'origine reste réglée
payment.voidedPaiement autorisé annulé avant capture
payment.void_failedTentative d'annulation sur un paiement autorisé échouée

Événements d'abonnement

ÉvénementQuand
subscription.createdUn nouvel abonnement a été créé (en trial ou active)
subscription.activeUn abonnement est passé à l'état active (p. ex. après fin d'essai)
subscription.trialingEssai démarré — pas de débit encore
subscription.chargedCycle de renouvellement réussi
subscription.charge_failedRenouvellement refusé — le dunning démarre selon le calendrier du marchand
subscription.cancelledAbonnement annulé (par le marchand, le client, ou après dunning)
subscription.updatedMoyen de paiement ou calendrier modifié

Événements de session de paiement

ÉvénementQuand
checkout.session.expiredSession ouverte au-delà de expires_at balayée (cron toutes les 5 min)
checkout.cancelledClient a cliqué Annuler sur la page de checkout hébergée

Charge utile

json
{
  "event_id": "evt_abc123",
  "event_type": "payment.completed",
  "payload_redacted": {
    "transaction_id": "txn_789xyz",
    "receipt_number": "RCT-000042",
    "amount": 2500,
    "currency": "USD",
    "status": "captured",
    "subtotal": 2300,
    "tax_amount": 200,
    "tax_name": "VAT",
    "payment_method_type": "card",
    "card_brand": "Visa",
    "card_last4": "0071",
    "token_id": "tok_abc123",
    "description": "Commande #1042",
    "line_items": [
      { "name": "T-shirt", "quantity": 1, "unit_amount": 2300 }
    ],
    "customer": { "name": "Jane Doe", "email": "[email protected]" },
    "metadata": { "order_id": "1042", "source": "woocommerce" }
  }
}

payload_redacted est la copie sûre à journaliser — pas de PAN complet, pas de CVV, juste last4.

Disponibilité des champs par type d'événement

Champpayment.*payment.refundedsubscription.*checkout.*
transaction_id
receipt_number
amount, currency✅ (montant remboursé)
subtotal, tax_amount, tax_name
payment_method_type
card_brand, card_last4si cartesi cartesi carte
token_idsi tokenisési on-tokentoujours
line_items, description
customer
metadata✅ — metadata de l'appelant sur la session originale✅ — même metadata que la session originale (PAS la metadata.merchant_order_id du remboursement)
checkout_session_external_id
merchant_order_id (top-level sur remboursements)

metadata est renvoyé tel quel depuis l'appel POST /api/v1/checkout-sessions que vous avez fait — c'est ainsi que les plugins de boutique (WC / Give / Magento / Shopify) corrèlent les webhooks avec la commande d'origine. Définissez ce dont vous avez besoin (un order id, un SKU, un tag de campagne) à la création de session et vous le récupérerez sur chaque événement.

Événements de cycle d'abonnement

subscription.charged et subscription.cancelled portent la même forme que payment.* plus un bloc subscription :

json
"subscription": {
  "external_id": "sub_abc123",
  "plan_id": "plan_monthly_pro",
  "cycle": 7,
  "next_billing_date": "2026-06-12"
}

Événements de session de paiement

checkout.session.expired et checkout.cancelled omettent le champ transaction_id — il n'y en a pas car aucun débit n'a été tenté. Utilisez-les pour effacer les lignes pending orphelines dans votre boutique :

json
{
  "event_id": "evt_xyz",
  "event_type": "checkout.session.expired",
  "payload_redacted": {
    "checkout_session_external_id": "cs_abc123",
    "amount": 2500,
    "currency": "USD",
    "metadata": { "order_id": "1042", "source": "give" }
  }
}

Vérification de signature

Chaque livraison de webhook inclut quatre en-têtes :

En-têteRôle
X-GC-SignatureSignature HMAC-SHA256 encodée en hex
X-GC-TimestampTimestamp Unix de la tentative de livraison
X-GC-Event-IDID unique d'événement (evt_…) — aussi dans le corps, utile pour la recherche en logs
X-GC-Event-TypeLe type d'événement (p. ex. payment.completed)

Calculez la signature attendue côté serveur et comparez en temps constant :

expected = HMAC-SHA256("{timestamp}.{payload}", webhook_secret)

Un exemple PHP complet est dans Exemples de code → Webhook handler.

Rejetez les événements anciens

Rejetez les événements dont X-GC-Timestamp s'écarte de plus de 5 minutes de l'horloge. Cela bloque les attaques par rejeu même si une signature fuit.

Rotation du secret

Chaque endpoint webhook a un secret courant et un précédent. Pour faire tourner :

  1. Tableau de bord → Webhooks → Endpoint → Rotate secret. Un nouveau secret est généré ; l'ancien devient le secret "précédent".
  2. Les nouvelles livraisons sont signées avec le secret courant. Les rejeux d'événements antérieurs à la rotation sont signés avec le secret précédent (la plateforme suit secret_version par livraison).
  3. Votre récepteur doit accepter l'une ou l'autre signature pendant une fenêtre de transition — essayez d'abord le courant, repli sur le précédent en cas de mismatch.
  4. Après que toutes les livraisons en vol qui vous intéressent ont été drainées (24h est un plafond sûr), révoquez le secret précédent depuis le tableau de bord.

Calendrier de relances

Les livraisons échouées sont ré-essayées en backoff exponentiel :

1m → 5m → 30m → 2h → 12h → 24h

Jusqu'à 6 tentatives par événement. Une fois épuisées, l'événement est marqué failed dans le tableau de bord et vous pouvez le rejouer manuellement.

Une livraison est considérée échouée si votre endpoint renvoie une réponse non-2xx ou dépasse le timeout (limite de 10 secondes).

Idempotence côté receveur

Rendez votre handler idempotent sur event_id (ou transaction_id pour les événements de paiement). Le même événement peut être livré plusieurs fois — par exemple, quand notre relance et votre serveur répondent en même temps.

Suite

Released under the proprietary Genius Checkout license.