Documentation Index
Fetch the complete documentation index at: https://developer.jeko.africa/llms.txt
Use this file to discover all available pages before exploring further.
Vue d’ensemble
Cette page contient des exemples complets d’implémentation de webhooks JEKO dans différents langages. Tous les exemples incluent :- Vérification de la signature HMAC-SHA256
- Parsing du payload
- Traitement des événements
- Gestion d’erreurs
Node.js (Express)
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');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
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' });
}
// Parser le payload
const payload = JSON.parse(req.body.toString());
// Traiter l'événement
await handleWebhookEvent(payload);
// Répondre rapidement
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
async function handleWebhookEvent(payload) {
const { event, data } = payload;
if (event === 'transaction.completed') {
await handleTransactionCompleted(data);
} else {
console.log('Unknown event type:', event);
}
}
async function handleTransactionCompleted(data) {
console.log('Transaction completed:', data.id);
console.log('Type:', data.transactionType); // "payment" ou "transfer"
console.log('Status:', data.status); // "success" ou "error"
console.log('Amount:', data.amount);
console.log('Fees:', data.fees);
console.log('Store:', data.storeName);
console.log('Business:', data.businessName);
// Détails de la transaction (optionnels)
if (data.transactionDetails) {
console.log('Transaction ID:', data.transactionDetails.id);
console.log('Reference:', data.transactionDetails.reference);
if (data.transactionDetails.paymentLinkId) {
console.log('Payment Link ID:', data.transactionDetails.paymentLinkId);
}
}
if (data.transactionType === 'payment' && data.status === 'success') {
// Traiter un paiement réussi
console.log('Payment successful:', data.id);
console.log('Customer:', data.counterpartLabel);
console.log('Customer ID:', data.counterpartIdentifier);
// Mettre à jour votre base de données, envoyer un email de confirmation, etc.
} else if (data.transactionType === 'transfer' && data.status === 'success') {
// Traiter un transfert réussi
console.log('Transfer successful:', data.id);
console.log('Beneficiary:', data.counterpartLabel);
console.log('Beneficiary ID:', data.counterpartIdentifier);
// Mettre à jour votre base de données, notifier le bénéficiaire, etc.
} else if (data.transactionType === 'transfer' && data.status === 'error') {
// Traiter un transfert échoué
console.log('Transfer failed:', data.id);
// Gérer l'échec, notifier l'utilisateur, etc.
}
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
PHP
<?php
$webhookSecret = getenv('JEKO_WEBHOOK_SECRET');
// Récupérer le body brut
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_JEKO_SIGNATURE'] ?? '';
// Vérifier la signature
$expectedSignature = hash_hmac('sha256', $rawBody, $webhookSecret);
if (!hash_equals($expectedSignature, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Parser le payload
$payload = json_decode($rawBody, true);
// Traiter l'événement
handleWebhookEvent($payload);
// Répondre rapidement
http_response_code(200);
echo json_encode(['received' => true]);
function handleWebhookEvent($payload) {
$event = $payload['event'];
$data = $payload['data'];
if ($event === 'transaction.completed') {
handleTransactionCompleted($data);
} else {
error_log('Unknown event type: ' . $event);
}
}
function handleTransactionCompleted($data) {
error_log('Transaction completed: ' . $data['id']);
error_log('Type: ' . $data['transactionType']); // "payment" ou "transfer"
error_log('Status: ' . $data['status']); // "success" ou "error"
error_log('Amount: ' . $data['amount']['amount']);
error_log('Fees: ' . $data['fees']['amount']);
error_log('Store: ' . $data['storeName']);
error_log('Business: ' . $data['businessName']);
// Détails de la transaction (optionnels)
if (isset($data['transactionDetails'])) {
$details = $data['transactionDetails'];
if (isset($details['id'])) {
error_log('Transaction ID: ' . $details['id']);
}
if (isset($details['reference'])) {
error_log('Reference: ' . $details['reference']);
}
if (isset($details['paymentLinkId'])) {
error_log('Payment Link ID: ' . $details['paymentLinkId']);
}
}
if ($data['transactionType'] === 'payment' && $data['status'] === 'success') {
// Traiter un paiement réussi
error_log('Payment successful: ' . $data['id']);
error_log('Customer: ' . $data['counterpartLabel']);
error_log('Customer ID: ' . $data['counterpartIdentifier']);
// Mettre à jour votre base de données, envoyer un email de confirmation, etc.
} else if ($data['transactionType'] === 'transfer' && $data['status'] === 'success') {
// Traiter un transfert réussi
error_log('Transfer successful: ' . $data['id']);
error_log('Beneficiary: ' . $data['counterpartLabel']);
error_log('Beneficiary ID: ' . $data['counterpartIdentifier']);
// Mettre à jour votre base de données, notifier le bénéficiaire, etc.
} else if ($data['transactionType'] === 'transfer' && $data['status'] === 'error') {
// Traiter un transfert échoué
error_log('Transfer failed: ' . $data['id']);
// Gérer l'échec, notifier l'utilisateur, etc.
}
}
?>
Python (Flask)
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('JEKO_WEBHOOK_SECRET')
def verify_signature(raw_body, signature):
expected_signature = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
@app.route('/webhook', methods=['POST'])
def webhook():
try:
# Récupérer le body brut
raw_body = request.get_data()
signature = request.headers.get('Jeko-Signature', '')
# Vérifier la signature
if not verify_signature(raw_body, signature):
return jsonify({'error': 'Invalid signature'}), 401
# Parser le payload
payload = json.loads(raw_body)
# Traiter l'événement (en arrière-plan si nécessaire)
handle_webhook_event(payload)
# Répondre rapidement
return jsonify({'received': True}), 200
except Exception as e:
print(f'Webhook error: {e}')
return jsonify({'error': 'Internal server error'}), 500
def handle_webhook_event(payload):
event = payload.get('event')
data = payload.get('data')
if event == 'transaction.completed':
handle_transaction_completed(data)
else:
print(f'Unknown event type: {event}')
def handle_transaction_completed(data):
print(f"Transaction completed: {data['id']}")
print(f"Type: {data['transactionType']}") # "payment" ou "transfer"
print(f"Status: {data['status']}") # "success" ou "error"
print(f"Amount: {data['amount']['amount']}")
print(f"Fees: {data['fees']['amount']}")
print(f"Store: {data['storeName']}")
print(f"Business: {data['businessName']}")
# Détails de la transaction (optionnels)
if 'transactionDetails' in data and data['transactionDetails']:
details = data['transactionDetails']
if 'id' in details:
print(f"Transaction ID: {details['id']}")
if 'reference' in details:
print(f"Reference: {details['reference']}")
if 'paymentLinkId' in details:
print(f"Payment Link ID: {details['paymentLinkId']}")
if data['transactionType'] == 'payment' and data['status'] == 'success':
# Traiter un paiement réussi
print(f"Payment successful: {data['id']}")
print(f"Customer: {data['counterpartLabel']}")
print(f"Customer ID: {data['counterpartIdentifier']}")
# Mettre à jour votre base de données, envoyer un email de confirmation, etc.
elif data['transactionType'] == 'transfer' and data['status'] == 'success':
# Traiter un transfert réussi
print(f"Transfer successful: {data['id']}")
print(f"Beneficiary: {data['counterpartLabel']}")
print(f"Beneficiary ID: {data['counterpartIdentifier']}")
# Mettre à jour votre base de données, notifier le bénéficiaire, etc.
elif data['transactionType'] == 'transfer' and data['status'] == 'error':
# Traiter un transfert échoué
print(f"Transfer failed: {data['id']}")
# Gérer l'échec, notifier l'utilisateur, etc.
if __name__ == '__main__':
app.run(port=3000)
Ruby (Sinatra)
require 'sinatra'
require 'json'
require 'openssl'
WEBHOOK_SECRET = ENV['JEKO_WEBHOOK_SECRET']
def verify_signature(raw_body, signature)
expected_signature = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha256'),
WEBHOOK_SECRET,
raw_body
)
# Comparaison sécurisée pour éviter les attaques par timing
return false if expected_signature.length != signature.length
result = 0
expected_signature.bytes.zip(signature.bytes) do |x, y|
result |= x ^ y
end
result == 0
rescue
false
end
post '/webhook' do
begin
# Récupérer le body brut
raw_body = request.body.read
signature = request.env['HTTP_JEKO_SIGNATURE'] || ''
# Vérifier la signature
unless verify_signature(raw_body, signature)
status 401
return { error: 'Invalid signature' }.to_json
end
# Parser le payload
payload = JSON.parse(raw_body)
# Traiter l'événement
handle_webhook_event(payload)
# Répondre rapidement
status 200
{ received: true }.to_json
rescue => e
puts "Webhook error: #{e.message}"
status 500
{ error: 'Internal server error' }.to_json
end
end
def handle_webhook_event(payload)
event = payload['event']
data = payload['data']
if event == 'transaction.completed'
handle_transaction_completed(data)
else
puts "Unknown event type: #{event}"
end
end
def handle_transaction_completed(data)
puts "Transaction completed: #{data['id']}"
puts "Type: #{data['transactionType']}" # "payment" ou "transfer"
puts "Status: #{data['status']}" # "success" ou "error"
puts "Amount: #{data['amount']['amount']}"
puts "Fees: #{data['fees']['amount']}"
puts "Store: #{data['storeName']}"
puts "Business: #{data['businessName']}"
# Détails de la transaction (optionnels)
if data['transactionDetails']
details = data['transactionDetails']
puts "Transaction ID: #{details['id']}" if details['id']
puts "Reference: #{details['reference']}" if details['reference']
puts "Payment Link ID: #{details['paymentLinkId']}" if details['paymentLinkId']
end
if data['transactionType'] == 'payment' && data['status'] == 'success'
# Traiter un paiement réussi
puts "Payment successful: #{data['id']}"
puts "Customer: #{data['counterpartLabel']}"
puts "Customer ID: #{data['counterpartIdentifier']}"
# Mettre à jour votre base de données, envoyer un email de confirmation, etc.
elsif data['transactionType'] == 'transfer' && data['status'] == 'success'
# Traiter un transfert réussi
puts "Transfer successful: #{data['id']}"
puts "Beneficiary: #{data['counterpartLabel']}"
puts "Beneficiary ID: #{data['counterpartIdentifier']}"
# Mettre à jour votre base de données, notifier le bénéficiaire, etc.
elsif data['transactionType'] == 'transfer' && data['status'] == 'error'
# Traiter un transfert échoué
puts "Transfer failed: #{data['id']}"
# Gérer l'échec, notifier l'utilisateur, etc.
end
end
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 Jeko Cockpit
# Test manuel du webhook
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "Jeko-Signature: your_test_signature" \
-d '{
"event": "transaction.completed",
"data": {
"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-15T14:30:25.000Z",
"transactionDetails": {
"id": "d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
"reference": "TEST-001",
"paymentLinkId": "abc123def456"
}
},
"timestamp": "2024-01-15T14:30:25.000Z"
}'
Points importants
- 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 : Répondez rapidement (dans les 5 secondes) pour éviter les retries
- 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