Webhooks Disbursement
AWDPay envoie des notifications en temps réel lorsque le statut de votre retrait change.
Configuration
Configurez votre URL webhook lors de la création du retrait via le paramètre callbackUrl :
curl -X POST "https://app.awdpay.com/api/withdraws/initiate" \
-H "Authorization: Bearer $AWDPAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XOF",
"beneficiaryName": "Amadou Diallo",
"beneficiaryPhone": "+221770123456",
"country": "SN",
"callbackUrl": "https://your-server.com/webhooks/disbursements"
}'
Événements supportés
| Événement | Description | Quand |
|---|---|---|
withdrawal.pending | Retrait créé | Immédiatement après création |
withdrawal.processing | Traitement en cours | Quand la passerelle démarre le traitement |
withdrawal.success | ✅ Transfert réussi | Fonds reçus par le bénéficiaire |
withdrawal.failed | ❌ Transfert échoué | Erreur pendant le transfert |
Format de charge utile
Exemple : Retrait réussi
{
"event": "withdrawal.success",
"timestamp": "2025-01-15T10:30:45Z",
"data": {
"reference": "WTD1704067200000ABC123",
"status": "success",
"amount": 5000.00,
"currency": "XOF",
"beneficiaryName": "Amadou Diallo",
"beneficiaryPhone": "+221770123456",
"gatewayName": "wave-senegal",
"createdAt": "2025-01-15T10:30:00Z",
"processedAt": "2025-01-15T10:30:45Z",
"externalReference": "WAVE_TXN_987654",
"metadata": {
"orderReference": "PAYOUT-8831",
"userId": "usr_12345"
}
}
}
Exemple : Retrait échoué
{
"event": "withdrawal.failed",
"timestamp": "2025-01-15T10:31:00Z",
"data": {
"reference": "WTD1704067200000DEF456",
"status": "failed",
"amount": 10000.00,
"currency": "XOF",
"beneficiaryName": "Fatou Ndiaye",
"beneficiaryPhone": "+221771234567",
"gatewayName": "orange-money-sn",
"createdAt": "2025-01-15T10:28:00Z",
"processedAt": "2025-01-15T10:31:00Z",
"failureReason": "insufficient_beneficiary_account",
"failureMessage": "Compte bénéficiaire non éligible",
"metadata": {
"orderReference": "PAYOUT-8832"
}
}
}
Champs de charge utile
| Champ | Type | Description |
|---|---|---|
event | String | Type d'événement |
timestamp | DateTime | Date/heure de l'événement |
data.reference | String | Référence du retrait |
data.status | String | Statut actuel |
data.amount | Double | Montant |
data.currency | String | Devise |
data.beneficiaryName | String | Nom du bénéficiaire |
data.beneficiaryPhone | String | Numéro de téléphone |
data.gatewayName | String | Passerelle |
data.createdAt | DateTime | Date de création |
data.processedAt | DateTime | Date de traitement |
data.externalReference | String | Référence externe (passerelle) |
data.failureReason | String | Code d'erreur (si échoué) |
data.failureMessage | String | Message d'erreur (si échoué) |
data.metadata | Object | Vos données personnalisées |
Sécurité webhook
Vérification obligatoire
AWDPay signe chaque webhook avec une signature HMAC-SHA256. Vérifiez toujours la signature avant de traiter la charge utile.
En-têtes de sécurité
| En-tête | Description |
|---|---|
X-AWDPay-Signature | Signature HMAC-SHA256 de la charge utile |
X-AWDPay-Timestamp | Horodatage d'envoi (Unix) |
Vérification de signature
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp, secret) {
// 1. Vérifier que l'horodatage n'est pas trop ancien (5 min max)
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime - parseInt(timestamp) > 300) {
throw new Error('Horodatage webhook expiré');
}
// 2. Construire le message à signer
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
// 3. Calculer la signature attendue
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// 4. Comparer de manière sécurisée
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Exemple complet (Express.js)
const express = require('express');
const app = express();
app.post('/webhooks/disbursements', express.json(), (req, res) => {
const signature = req.headers['x-awdpay-signature'];
const timestamp = req.headers['x-awdpay-timestamp'];
const payload = req.body;
// Vérifier la signature
try {
const isValid = verifyWebhookSignature(
payload,
signature,
timestamp,
process.env.AWDPAY_WEBHOOK_SECRET
);
if (!isValid) {
console.error('Signature webhook invalide');
return res.status(401).send('Signature invalide');
}
} catch (error) {
console.error('Échec vérification webhook:', error.message);
return res.status(401).send(error.message);
}
// Traiter l'événement
const { event, data } = payload;
switch (event) {
case 'withdrawal.success':
handleSuccessfulWithdrawal(data);
break;
case 'withdrawal.failed':
handleFailedWithdrawal(data);
break;
case 'withdrawal.processing':
handleProcessingWithdrawal(data);
break;
}
// Toujours répondre 200 rapidement
res.status(200).send('OK');
});
async function handleSuccessfulWithdrawal(data) {
const { reference, amount, beneficiaryName, metadata } = data;
// Mettre à jour votre base de données
await db.payouts.update({
where: { orderReference: metadata.orderReference },
data: {
status: 'completed',
awdpayReference: reference,
completedAt: new Date()
}
});
// Notifier le bénéficiaire
await sendSMS(data.beneficiaryPhone,
`Vous avez reçu ${amount} XOF d'AWDPay.`
);
}
async function handleFailedWithdrawal(data) {
const { reference, failureReason, metadata } = data;
// Enregistrer l'échec
console.error(`Retrait ${reference} échoué : ${failureReason}`);
// Mettre à jour et alerter
await db.payouts.update({
where: { orderReference: metadata.orderReference },
data: {
status: 'failed',
failureReason: failureReason
}
});
// Alerter l'équipe support
await alertSupport(`Paiement échoué : ${reference}`);
}
Codes d'erreur webhook
| Code | Description | Action recommandée |
|---|---|---|
insufficient_beneficiary_account | Compte bénéficiaire insuffisant/inactif | Vérifier le numéro |
invalid_phone_number | Numéro de téléphone invalide | Corriger le format |
daily_limit_exceeded | Limite quotidienne atteinte | Réessayer demain |
operator_unavailable | Opérateur temporairement indisponible | Réessayer plus tard |
beneficiary_not_registered | Bénéficiaire non enregistré | Utiliser un autre numéro |
transaction_rejected | Transaction rejetée par l'opérateur | Contacter le support |
Meilleures pratiques
✅ À faire
- Répondre rapidement — Retourner
200 OKen moins de 5 secondes - Traiter de manière asynchrone — Mettre les tâches lourdes dans une file d'attente
- Idempotence — Gérer les doublons (même
reference) - Journaliser — Conserver tous les webhooks reçus
❌ À ne pas faire
- Ne pas bloquer — Éviter les opérations longues dans le gestionnaire
- Ne pas ignorer les erreurs — Journaliser et alerter sur les échecs
- Ne pas faire confiance aveuglément — Toujours vérifier la signature
Gestion des nouvelles tentatives
AWDPay réessaie d'envoyer les webhooks en cas d'échec :
| Tentative | Délai |
|---|---|
| 1 | Immédiat |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 heures |
Après 5 tentatives échouées, le webhook est marqué comme définitivement échoué.
Prochaine étape
➡️ Aperçu — Retour à la documentation principale