Aller au contenu principal

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énementDescriptionQuand
withdrawal.pendingRetrait crééImmédiatement après création
withdrawal.processingTraitement en coursQuand la passerelle démarre le traitement
withdrawal.success✅ Transfert réussiFonds 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

ChampTypeDescription
eventStringType d'événement
timestampDateTimeDate/heure de l'événement
data.referenceStringRéférence du retrait
data.statusStringStatut actuel
data.amountDoubleMontant
data.currencyStringDevise
data.beneficiaryNameStringNom du bénéficiaire
data.beneficiaryPhoneStringNuméro de téléphone
data.gatewayNameStringPasserelle
data.createdAtDateTimeDate de création
data.processedAtDateTimeDate de traitement
data.externalReferenceStringRéférence externe (passerelle)
data.failureReasonStringCode d'erreur (si échoué)
data.failureMessageStringMessage d'erreur (si échoué)
data.metadataObjectVos 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êteDescription
X-AWDPay-SignatureSignature HMAC-SHA256 de la charge utile
X-AWDPay-TimestampHorodatage 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

CodeDescriptionAction recommandée
insufficient_beneficiary_accountCompte bénéficiaire insuffisant/inactifVérifier le numéro
invalid_phone_numberNuméro de téléphone invalideCorriger le format
daily_limit_exceededLimite quotidienne atteinteRéessayer demain
operator_unavailableOpérateur temporairement indisponibleRéessayer plus tard
beneficiary_not_registeredBénéficiaire non enregistréUtiliser un autre numéro
transaction_rejectedTransaction rejetée par l'opérateurContacter le support

Meilleures pratiques

✅ À faire

  • Répondre rapidement — Retourner 200 OK en 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 :

TentativeDélai
1Immédiat
21 minute
35 minutes
430 minutes
52 heures

Après 5 tentatives échouées, le webhook est marqué comme définitivement échoué.


Prochaine étape

➡️ Aperçu — Retour à la documentation principale