> ## Documentation Index
> Fetch the complete documentation index at: https://docs.caurisflux.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Recevez des notifications en temps réel sur vos transactions

Les webhooks sont des notifications HTTP envoyées par CaurisFlux vers votre serveur lorsqu'un événement se produit.

<Info>
  Les webhooks sont **essentiels** pour une intégration fiable. Ne vous fiez pas uniquement aux redirections.
</Info>

## Configuration

1. Allez dans **Dashboard > Paramètres > Webhooks**
2. Cliquez sur **Ajouter un endpoint** (HTTPS requis en production)
3. Sélectionnez les événements à recevoir
4. Copiez le **secret de signature**

## Événements

| Événement           | Description            |
| ------------------- | ---------------------- |
| `payment.completed` | Paiement réussi        |
| `payment.failed`    | Paiement échoué        |
| `payment.cancelled` | Paiement annulé        |
| `payment.expired`   | Paiement expiré        |
| `payout.completed`  | Payout réussi          |
| `payout.failed`     | Payout échoué          |
| `payout.cancelled`  | Payout annulé          |
| `refund.completed`  | Remboursement effectué |

## Structure

### Headers

| Header               | Description                           |
| -------------------- | ------------------------------------- |
| `X-Cauris-Signature` | Signature HMAC (format: `sha256=xxx`) |
| `X-Cauris-Timestamp` | Timestamp Unix de l'envoi             |
| `Content-Type`       | `application/json`                    |

### Exemples de payload

<CodeGroup>
  ```json payment.completed theme={null}
  {
    "event": "payment.completed",
    "timestamp": "2024-01-15T12:05:30.000Z",
    "data": {
      "transactionId": "TX000007272",
      "status": "completed",
      "amount": 10000,
      "currency": "XOF",
      "fees": 150,
      "netAmount": 9850,
      "provider": "wave",
      "providerReference": "WAVE-TXN-123456",
      "externalReference": "COMMANDE-12345",
      "customerPhone": "+221771234567",
      "customerName": "Amadou Diallo",
      "paidAt": "2024-01-15T12:05:30.000Z",
      "metadata": {
        "orderId": "12345"
      }
    }
  }
  ```

  ```json payment.failed theme={null}
  {
    "event": "payment.failed",
    "timestamp": "2024-01-15T12:02:00.000Z",
    "data": {
      "transactionId": "TX000007272",
      "status": "failed",
      "amount": 10000,
      "currency": "XOF",
      "provider": "wave",
      "externalReference": "COMMANDE-12345",
      "failureReason": "insufficient_funds",
      "failureMessage": "Solde insuffisant sur le compte Wave",
      "failedAt": "2024-01-15T12:02:00.000Z",
      "metadata": {
        "orderId": "12345"
      }
    }
  }
  ```

  ```json payout.completed theme={null}
  {
    "event": "payout.completed",
    "timestamp": "2024-01-15T14:02:00.000Z",
    "data": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "payoutId": "PAY000001234",
      "externalRef": "PAY-2024-001",
      "status": "completed",
      "amount": 50000,
      "currency": "XOF",
      "fees": 250,
      "netAmount": 49750,
      "method": "mobile_money",
      "mobileNumber": "+221771234567",
      "mobileProvider": "wave",
      "providerReference": "WAVE-PAY-123456",
      "sourceBalanceCategory": "payout",
      "completedAt": "2024-01-15T14:02:00.000Z",
      "metadata": {
        "supplierId": "SUP-123"
      }
    }
  }
  ```

  ```json payout.failed theme={null}
  {
    "event": "payout.failed",
    "timestamp": "2024-01-15T14:05:00.000Z",
    "data": {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "payoutId": "PAY000001235",
      "externalRef": "PAY-2024-003",
      "status": "failed",
      "amount": 100000,
      "currency": "XOF",
      "method": "mobile_money",
      "mobileNumber": "+221770000000",
      "mobileProvider": "wave",
      "errorCode": "invalid_recipient",
      "errorMessage": "Le numéro n'est pas associé à un compte Wave actif",
      "retryCount": 3,
      "maxRetries": 3
    }
  }
  ```
</CodeGroup>

## Vérification de la signature

<Warning>
  Vérifiez **toujours** la signature pour éviter les faux webhooks.
</Warning>

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(payload, signature, secret) {
    const expected = 'sha256=' + crypto
      .createHmac('sha256', secret)
      .update(payload)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  }

  // Dans votre handler Express
  app.post('/webhooks/cauris', (req, res) => {
    const signature = req.headers['x-cauris-signature'];
    const isValid = verifyWebhook(
      JSON.stringify(req.body),
      signature,
      process.env.WEBHOOK_SECRET
    );

    if (!isValid) {
      return res.status(401).send('Invalid signature');
    }

    // Répondre immédiatement
    res.status(200).send('OK');

    // Traiter en arrière-plan
    processWebhook(req.body);
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
      expected = 'sha256=' + hmac.new(
          secret.encode(),
          payload,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
  ```

  ```php PHP theme={null}
  function verifyWebhook($payload, $signature, $secret) {
      $expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
      return hash_equals($expected, $signature);
  }
  ```
</CodeGroup>

## Codes d'erreur

### Paiements

| Code                 | Description              |
| -------------------- | ------------------------ |
| `insufficient_funds` | Solde insuffisant        |
| `user_cancelled`     | Annulé par l'utilisateur |
| `timeout`            | Délai expiré             |
| `provider_error`     | Erreur du provider       |
| `invalid_phone`      | Numéro invalide          |

### Payouts

| Code                   | Description                      |
| ---------------------- | -------------------------------- |
| `invalid_recipient`    | Bénéficiaire invalide            |
| `insufficient_balance` | Solde insuffisant (votre compte) |
| `provider_error`       | Erreur du provider               |
| `limit_exceeded`       | Limite dépassée                  |
| `account_blocked`      | Compte bloqué                    |

## Retries

Si votre serveur ne repond pas `2xx`, CaurisFlux réessaie :

| Tentative | Délai      |
| --------- | ---------- |
| 1         | Immédiat   |
| 2         | 1 minute   |
| 3         | 5 minutes  |
| 4         | 30 minutes |
| 5         | 2 heures   |

Après 5 tentatives, le webhook est marqué comme non livré.

## Bonnes pratiques

<Steps>
  <Step title="Répondez en < 5 secondes">
    Retournez `200 OK` immédiatement, traitez en arrière-plan.
  </Step>

  <Step title="Idempotence">
    Stockez les `transactionId` / `payoutId` traités pour éviter les doublons.
  </Step>

  <Step title="HTTPS obligatoire">
    Votre endpoint doit être en HTTPS avec un certificat valide.
  </Step>

  <Step title="Loggez tout">
    Conservez les webhooks reçus pour debug et audit.
  </Step>
</Steps>

## Tester

Depuis le Dashboard : **Webhooks > Envoyer un test**

Ou utilisez les numéros de test en sandbox pour déclencher des webhooks réels.
