Webhooks
Exemples de code Webhooks
Exemples d'implémentation de webhooks Jèko dans différents langages
Cette page contient des exemples complets d'implémentation de webhooks Jèko dans différents langages. Tous les exemples incluent :
- Vérification de la signature HMAC-SHA256
- Brancher sur l'en-tête
Jeko-Event. Les corps sont dans Événements - Traitement des événements
- Gestion d'erreurs
Serveur webhook
Le même serveur, dans quatre langages. Choisissez votre onglet.
const express = require('express');
const crypto = require('crypto');
const app = express();
// Middleware pour parser le body brut (important pour la vérification de signature)
app.use('/webhook', express.raw({ type: 'application/json' }));
const WEBHOOK_SECRET = process.env.JEKO_WEBHOOK_SECRET;
function verifySignature(rawBody, signature) {
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const received = Buffer.from(signature);
const expected = Buffer.from(expectedSignature);
// timingSafeEqual lève une exception si les longueurs diffèrent, ce qui est
// justement le cas d'une signature falsifiée : comparer les tailles d'abord.
if (received.length !== expected.length) return false;
return crypto.timingSafeEqual(received, expected);
}
app.post('/webhook', async (req, res) => {
try {
// Vérifier la signature
const signature = req.headers['jeko-signature'];
if (!signature || !verifySignature(req.body, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// TRANSACTION_COMPLETED est plat. SERVICE_PROVIDER_LINK_REQUEST est aussi
// un corps plat : l'événement est l'en-tête Jeko-Event. HMAC porte sur le body brut.
const body = JSON.parse(req.body.toString());
const event = req.headers['jeko-event'];
// Accuser réception avant de traiter
res.status(200).json({ received: true });
if (event === 'SERVICE_PROVIDER_LINK_REQUEST') {
await handleLinkRequest(body);
} else if (body.event) {
await handleEscrow(body);
} else {
await handleTransaction(body);
}
} catch (error) {
console.error('Webhook error:', error);
// Le 200 est déjà parti avant le traitement : répondre à nouveau lèverait
// ERR_HTTP_HEADERS_SENT. On journalise, et on rejoue de son côté.
if (!res.headersSent) {
res.status(500).json({ error: 'Internal server error' });
}
}
});
async function handleTransaction(transaction) {
console.log('Transaction:', transaction.id);
console.log('Type:', transaction.transactionType);
console.log('Status:', transaction.status); // "pending", "success" ou "error"
console.log('Amount:', transaction.amount);
console.log('Fees:', transaction.fees);
console.log('Store:', transaction.storeName);
console.log('Business:', transaction.businessName);
console.log('Executed at:', transaction.executedAt); // "2024-01-15 14:30:25"
// Détails de la transaction (champs optionnels)
const details = transaction.transactionDetails ?? {};
if (details.id) console.log('Payment request ID:', details.id);
if (details.reference) console.log('Reference:', details.reference);
if (details.paymentLinkId) console.log('Payment Link ID:', details.paymentLinkId);
if (transaction.status === 'success') {
console.log('Transaction réussie:', transaction.id);
console.log('Contrepartie:', transaction.counterpartLabel);
console.log('Identifiant:', transaction.counterpartIdentifier);
// Mettre à jour votre base de données, envoyer une confirmation, etc.
} else if (transaction.status === 'error') {
console.log('Transaction échouée:', transaction.id);
// Gérer l'échec, notifier l'utilisateur, etc.
}
}
async function handleLinkRequest(payload) {
console.log('Link request:', payload.id, payload.status);
}
async function handleEscrow(body) {
console.log('Escrow:', body.event, body.escrowId, body.status);
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});Test avec cURL
# Exemple de test local avec ngrok
# 1. Démarrer votre serveur local
# 2. Exposer avec ngrok: ngrok http 3000
# 3. Configurer l'URL ngrok dans le Dashboard Business
# Test manuel du webhook
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "Jeko-Signature: your_test_signature" \
-d '{
"id": "txn_test123",
"amount": {
"amount": 10000,
"currency": "XOF"
},
"fees": {
"amount": 100,
"currency": "XOF"
},
"status": "success",
"counterpartLabel": "John Doe",
"counterpartIdentifier": "+2250701234567",
"paymentMethod": "wave",
"transactionType": "payment",
"businessName": "Ma Boutique",
"storeName": "Magasin Principal",
"description": "Test payment",
"executedAt": "2024-01-15 14:30:25",
"transactionDetails": {
"id": "d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
"reference": "TEST-001",
"paymentLinkId": "abc123def456"
}
}'Cinq erreurs à ne pas commettre
- Body brut : Utilisez toujours le body brut (raw body) pour calculer la signature, pas le JSON parsé
- Comparaison sécurisée : Utilisez une comparaison sécurisée (timing-safe) pour éviter les attaques par timing
- Réponse rapide : Accusez réception sans attendre la fin du traitement. Au-delà de 30 secondes, le webhook est réessayé
- Traitement asynchrone : Pour les traitements longs, acceptez le webhook immédiatement et traitez-le en arrière-plan
- Idempotence : Assurez-vous que le traitement est idempotent pour éviter les doublons