Webhooks
Coinetic informe votre serveur de chaque changement d'une facture par une requête POST. Un webhook est un signal « allez consulter la facture », pas une preuve de paiement.
Configuration
- Tableau de bord → « Clés et webhooks » → « Ajouter ». L'URL doit être en
https://, vers une adresse publique sur le port 443 (en sandbox,http://localhostest aussi accepté). - Le secret de l'endpoint n'est affiché qu'une seule fois. Jusqu'à 5 endpoints actifs : chaque événement est envoyé à tous.
Requête
POST /webhooks/coinetic HTTP/1.1
Content-Type: application/json
User-Agent: cryptopay-webhooks/1
X-Webhook-Id: evt_3f9c…
X-Webhook-Timestamp: 1760000000
X-Webhook-Signature: v1=5d1e…
{"id":"evt_3f9c…","type":"invoice.paid","created_at":"2026-10-12T15:04:05Z","livemode":true,"data":{"invoice_id":"inv_x7k2…","order_id":"ORDER-1001","status":"paid","paid_amount":"49990000"}}X-Webhook-Id reste identique d'une relance à l'autre. Pendant une rotation du secret, X-Webhook-Signature contient deux signatures séparées par une virgule. Les noms techniques (cryptopay-webhooks/1) sont historiques : inutile de les modifier de votre côté.
Vérification de la signature
expected = "v1=" + hex(HMAC-SHA256(webhook_secret, X-Webhook-Timestamp + "." + raw_body))- Prenez le corps brut, c'est-à-dire les octets avant tout parsing JSON.
- Vérifiez que
X-Webhook-Timestampne s'écarte pas de l'heure actuelle de plus de 5 minutes. - Découpez
X-Webhook-Signaturesur la virgule. La signature est valide si l'une quelconque des parties correspond à la valeur attendue (comparaison en temps constant). - Aucune correspondance ? Répondez
401et ne faites rien.
Valeurs de référence : secret whsec-0123456789abcdef0123456789abcdef, timestamp 1760000000, corps {"id":"evt_1","type":"invoice.paid"} → v1=aceeb56215ba09e7a2923d48f89afb8331f422f2d0a8cf60f76fef69fe06a6a4.
Traitement
vérifier la signature → dédupliquer par X-Webhook-Id → GET /v1/invoices/{invoice_id}
→ mettre à jour la commande selon le statut de l'API (livraison une seule fois) → répondre 2xx- Déduplication. Un même événement peut arriver plusieurs fois. Conservez les
X-Webhook-Iddéjà traités ; positionnez l'indicateur « livré » dans la même transaction que la livraison. - Répondez vite : en moins de 10 secondes. Placez les traitements longs dans une file d'attente.
- Codes de réponse :
2xx= reçu ;410 Gone= ne pas relancer cette livraison ; tout autre code (y compris3xx: les redirections ne sont pas suivies) ou un délai dépassé = relance. - Ignorez les champs et types d'événements inconnus : la liste va s'allonger.
Des handlers prêts à l'emploi en quatre langages sont disponibles dans le démarrage rapide.
Relances
10 s → 30 s → 2 min → 10 min → 30 min → 1 h → 3 h ×7 → 1 h, avec une légère variation aléatoire. La dernière tentative a lieu environ 24 heures plus tard. Une livraison échouée peut être renvoyée manuellement : tableau de bord → « Webhooks » → « Renvoyer ».
Ordre de livraison
Les événements d'une même facture sont livrés à un endpoint dans l'ordre : le suivant ne part pas tant que la livraison précédente attend une relance. Qu'elle aboutisse ou échoue, les suivants partent ensuite. Fiez-vous donc au statut renvoyé par GET /v1/invoices/{id}, et non à l'enchaînement des événements. Les événements de factures différentes sont indépendants.
Types d'événements
| Événement | Quand |
|---|---|
invoice.detected | Le paiement est visible sur le réseau |
invoice.confirming | Le paiement est inclus dans un bloc, les confirmations sont en cours |
invoice.paid | Payé et confirmé : vous pouvez livrer (après revérification). data.late: true : un stablecoin est arrivé après expiration et a été crédité automatiquement |
invoice.overpaid | Payé avec un excédent |
invoice.underpaid | Montant confirmé inférieur au montant dû |
invoice.awaiting_payment | La facture attend de nouveau un paiement (par exemple après le réorg de son unique paiement) |
invoice.expired | Délai écoulé sans paiement |
invoice.late_payment | Paiement en actif volatil reçu après expiration |
invoice.manual_review | Paiement en cours de contrôle AML |
invoice.payment_held | Paiement bloqué par le contrôle AML (quarantaine) |
invoice.reorged | Le bloc contenant le paiement a été annulé, le statut a été recalculé (par exemple paid → confirming) |
invoice.payment_after_close | Paiement reçu sur une facture déjà clôturée : il ne règle pas la commande et peut être remboursé |
invoice.cancelled | Facture clôturée sans paiement, par exécution d'un remboursement |
refund.sent | Remboursement exécuté ; data.refund contient le montant, l'adresse et le tx_hash |
Les événements de facturation (billing.*) concernent les factures de commission de la plateforme : émission, paiement, retard, suspension et levée de la suspension.
invoice.reorgedUn réorg après paid est rare, mais il arrive. Si vous livrez instantanément, abonnez-vous à cet événement et transmettez ces cas à votre support.Rotation du secret
Tableau de bord → « Changer le secret » : Coinetic se met à signer avec les deux secrets.
- Ajoutez le nouveau secret sur votre serveur (acceptez les deux).
- Cliquez sur « Terminer la rotation » : l'ancien est supprimé.
- Retirez l'ancien secret de votre serveur.