> ## 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.

# Exemples de code Webhooks

> Exemples d'implémentation de webhooks JEKO dans différents langages

## 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)

<CodeGroup>
  ```javascript Node.js theme={null}
  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');
  });
  ```
</CodeGroup>

## PHP

<CodeGroup>
  ```php PHP theme={null}
  <?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.
      }
  }
  ?>
  ```
</CodeGroup>

## Python (Flask)

<CodeGroup>
  ```python Python theme={null}
  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)
  ```
</CodeGroup>

## Ruby (Sinatra)

<CodeGroup>
  ```ruby Ruby theme={null}
  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
  ```
</CodeGroup>

## Test avec cURL

<CodeGroup>
  ```bash cURL theme={null}
  # 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"
    }'
  ```
</CodeGroup>

## Points importants

1. **Body brut** : Utilisez toujours le body brut (raw body) pour calculer la signature, pas le JSON parsé
2. **Comparaison sécurisée** : Utilisez une comparaison sécurisée (timing-safe) pour éviter les attaques par timing
3. **Réponse rapide** : Répondez rapidement (dans les 5 secondes) pour éviter les retries
4. **Traitement asynchrone** : Pour les traitements longs, acceptez le webhook immédiatement et traitez-le en arrière-plan
5. **Idempotence** : Assurez-vous que le traitement est idempotent pour éviter les doublons
