Webhook
Les webhooks permettent à votre système de recevoir des notifications automatiques en temps réel de PayerScan. Le système envoie des requêtes POST à l'URL callback_url fournie par le Merchant lors de la création d'une facture.
Types d'événements
PayerScan a 2 événements principaux retournés dans le champ status :
completed— Paiement réussi : Une transaction correspondante a été confirmée avec la facture (blockchain ou Binance Pay).expired— Facture expirée : La facture a dépassé le temps d'attente de paiement.
Les marchands doivent exposer une URL publique (HTTPS recommandé), recevoir les requêtes POST, traiter le payload et retourner HTTP 2xx dans le délai spécifié (par ex. 10 secondes).
Structure du Payload
1. Webhook lors d'un paiement réussi (status : completed)
Lorsqu'une transaction correspondante est trouvée (adresse correcte, montant correct, dans la fenêtre temporelle), le système met à jour la facture en completed et envoie un POST à callback_url avec ce payload :
Payload (JSON body)
{
"merchant_id": "MERCHANT_001",
"api_key": "YOUR_API_KEY",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "completed",
"amount": "100",
"token_symbol": "USDT",
"token_price": "1",
"token_amount": "100.0026",
"network_symbol": "BSC",
"from_address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3",
"to_address": "0xc221460115e2CfCa5bF089A7e647b11cb9631efE",
"transaction_hash": "0xd346b32b83b35376d42a2464598fbf565fffb39e6569200f034a5e8342c532d7"
}
| Champ | Description |
|---|---|
merchant_id | Code marchand de la boutique. |
api_key | API Key (pour votre vérification croisée si nécessaire). |
request_id | Votre ID de commande (pour comparer avec votre facture interne). |
trans_id | Code de la facture. Utilisez-le pour mettre à jour la commande correspondante. |
status | Toujours "completed". |
amount | Montant en USD (chaîne de caractères). |
token_symbol | Coin/Token (par ex. USDT). |
token_price | Taux de change au moment de la transaction (chaîne de caractères). Peut se replier sur amount si indisponible. |
token_amount | Montant réel en crypto transféré (chaîne de caractères). Peut être null dans de rares cas. |
network_symbol | Réseau (par ex. BSC, TRC20, ETH, SOL, BINANCE_ID, OKX_UID, BYBIT_UID, ...). |
from_address | Adresse du portefeuille de l'expéditeur. |
to_address | Adresse du portefeuille de réception (de la boutique). |
transaction_hash | Hash de la transaction sur la blockchain. |
Exigences du Merchant
- Recevoir le POST à l'endpoint correspondant à
callback_url. - Analyser le JSON body.
- Vérifier (optionnel mais recommandé) : comparer
merchant_idoutrans_idavec vos données internes. - Mettre à jour la commande : marquer la commande correspondant à
trans_idourequest_idcomme payée ; enregistrertransaction_hashsi nécessaire. - Retourner HTTP 2xx (par ex. 200 OK) dans le délai d'expiration (par ex. 10 secondes). Le corps de la réponse n'est pas requis ; le système n'a besoin que d'un statut 2xx pour considérer le webhook comme réussi.
Si vous retournez 4xx/5xx ou un timeout, le système marque le callback comme failed et dispose d'un mécanisme de réessai (le nombre de tentatives et les intervalles dépendent de la configuration backend).
2. Webhook lors de l'expiration d'une facture (status : expired)
Lorsqu'une facture passe à l'état expired (dépassement de expires_at), le système envoie un POST à callback_url avec un payload plus simple :
Payload (JSON body)
{
"merchant_id": "MERCHANT_001",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "expired",
"amount": "100"
}
| Champ | Description |
|---|---|
merchant_id | Code marchand de la boutique. |
request_id | Votre ID de commande (si fourni lors de la création de la facture). |
trans_id | Code de la facture. Utilisez-le pour mettre à jour la commande correspondante. |
status | Toujours "expired". |
amount | Montant en USD (chaîne de caractères). |
Vous pouvez utiliser cette information pour mettre à jour la commande comme « expirée », annuler les réservations, etc. Vous devez toujours retourner HTTP 2xx pour que le système ne la considère pas comme une erreur.
3. Exemple de gestionnaire Webhook (Node.js)
app.post('/webhook/payment', express.json(), async (req, res) => {
const { merchant_id, api_key, trans_id, request_id, status, amount, transaction_hash } = req.body;
// Vérifier merchant_id (completed et expired)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).send('Unauthorized');
}
// Vérifier api_key (completed uniquement — expired n'inclut pas api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).send('Unauthorized');
}
// Trouver la commande par request_id ou trans_id
const order = await orderService.findByRequestId(request_id) || await orderService.findByTransId(trans_id);
if (!order) {
return res.status(404).send('Order not found');
}
if (status === 'completed') {
await orderService.markPaid(order, { transaction_hash, amount });
} else if (status === 'expired') {
await orderService.markExpired(order);
}
res.status(200).send('OK');
});
Vérification de signature
Pour vous assurer que les webhooks proviennent réellement de PayerScan, vous devez vérifier la signature ou effectuer une vérification croisée des données.
- HTTPS : Utilisez toujours HTTPS pour
callback_urlafin d'empêcher les écoutes. - Vérification de l'API Key (completed uniquement) : Le webhook
completedinclut votreapi_keydans le payload. Comparez-la avec votre API key stockée pour vérifier l'authenticité. Remarque : le webhookexpiredn'inclut pasapi_key.
Idempotence
Un trans_id ne peut recevoir qu'un seul événement completed, mais les réessais peuvent le renvoyer. Assurez-vous que votre logique de traitement est idempotente (les mises à jour multiples sont sûres, pas de double crédit).
Réessai en cas d'échec du Webhook
Lorsque le serveur du Merchant retourne une erreur (4xx/5xx) ou un timeout, le système réessaie le webhook selon ce calendrier :
| Tentative | Moment |
|---|---|
| Tentative 1 | Immédiatement (lors de la finalisation de la facture) |
| Tentative 2 | Après 10 secondes |
| Tentative 3 | Après 30 secondes |
| Tentative 4 | Après 30 secondes |
| Tentative 5 | Après 60 secondes |
Maximum 5 tentatives. Après 5 échecs, callback_status passe à failed et aucune tentative supplémentaire n'est effectuée.
Les marchands doivent s'assurer que leur endpoint est stable et retourne 2xx rapidement (< 10 secondes) pour éviter les réessais inutiles.