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

# Webhooks

> Écoutez les événements de paiement, de retrait et de compte en temps réel grâce aux livraisons HTTP POST signées.

# Webhooks

Inflow utilise les webhooks pour notifier votre application en temps réel lorsque des événements surviennent au sein de votre organisation (tels que la finalisation des règlements ou la confirmation des retraits).

## Signature des Livraisons et Gestion du Secret

Les livraisons peuvent être signées, mais uniquement si un secret est configuré sur l'adresse de destination (endpoint). Si aucun secret n'est défini (`hasSigningSecret: false`), les webhooks sont envoyés non signés.

* **Création & Rotation du Secret** : Lors de la création d'un endpoint webhook, un secret de signature peut être fourni ou généré automatiquement (`secret: "whsec_..."`). Les secrets générés par la plateforme ne sont renvoyés qu'une seule fois lors de la création. L'appel à `POST /organizations/{id}/webhooks/{endpointId}/rotate-secret` invalide le secret précédent immédiatement et renvoie le nouveau secret.
* **Vérification de Signature** : Lorsqu'un secret est actif, les livraisons sortantes incluent les en-têtes HTTP suivants :
  * `X-Webhook-Signature` : Format `sha256=<hex_digest>` (ou `v1=<hex_digest>`).
  * `X-Webhook-Timestamp` : Horodatage Unix (en secondes) de la tentative de livraison.
* **Calcul HMAC** : La signature est calculée par HMAC-SHA256 sur la chaîne exacte du corps brut de la requête (`{timestamp}.{rawBody}` ou chaîne JSON exacte). Ne parsez pas et ne ré-sérialisez pas le JSON avant vérification.

### Exemple de Vérification de Signature en Node.js

```js theme={null}
import crypto from "node:crypto";

export function verifyWebhook({ rawBody, headers, secret }) {
  const timestamp = headers["x-webhook-timestamp"];
  const signatureHeader = headers["x-webhook-signature"];

  if (!timestamp || !signatureHeader) return false;

  // Rejeter les requêtes de plus de 5 minutes pour prévenir les attaques par rejeu
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(timestamp)) > 300) return false;

  const hash = signatureHeader.replace(/^sha256=|^v1=/, "");
  const expectedHash = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`, "utf8")
    .digest("hex");

  return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expectedHash));
}
```

***

## Enveloppe d'Événement & Schémas des Données

Tous les webhooks sortants utilisent une structure d'enveloppe standardisée :

```json theme={null}
{
  "id": "evt_9876543210",
  "type": "payment.settled",
  "createdAt": "2026-07-25T08:30:00.000Z",
  "data": {
    "id": "c1f7b8a2-3e4d-4f1a-b2c3-5d6e7f8a9b0c",
    "reference": "ORD-2026-0042",
    "sourceAmount": 100.00,
    "sourceCurrency": "EUR",
    "targetAmount": 160000.00,
    "targetCurrency": "NGN",
    "exchangeRate": 1600.00,
    "status": "settled",
    "previousStatus": "executed"
  }
}
```

### Champs des Données de Paiement

Pour les événements de statut de paiement, l'objet `data` comprend :

* `id` : Identifiant unique du paiement (`uuid`)
* `reference` : Votre référence de paiement unique
* `sourceAmount` : Montant dans la devise source
* `sourceCurrency` : Code de devise source ISO 4217
* `targetAmount` : Montant dans la devise cible
* `targetCurrency` : Code de devise cible ISO 4217
* `exchangeRate` : Taux de change appliqué
* `status` : Statut actuel (`created`, `initiated`, `executed`, `settled`, `failed`, `cancelled`)
* `previousStatus` *(facultatif)* : Statut antérieur du paiement avant la transition

***

## Cycle de Vie du Paiement : `payment.executed` vs `payment.settled`

> **Important** : `payment.succeeded` n'est **pas** un nom d'événement sortant mis en œuvre.
> Les événements de succès réellement émis par la plateforme sont **`payment.executed`** et **`payment.settled`**.

* **`payment.executed`** : Le prestataire a confirmé la réception des fonds. Les fonds sont crédités sur le solde en attente de votre organisation.
* **`payment.settled`** : Les fonds ont terminé leur traitement de règlement et sont disponibles sur le solde de votre portefeuille.

**Recommandation** : L'exécution des commandes et le déblocage des biens numériques peuvent être effectués dès la réception de **`payment.executed`**, mais les retraits automatiques ne peuvent être déclenchés qu'une fois les fonds réglés (**`payment.settled`**).

***

## Catalogue des Événements Sortants

La plateforme définit les types d'événements webhook sortants suivants :

| Catégorie      | Type d'Événement     | Description                                                  |
| -------------- | -------------------- | ------------------------------------------------------------ |
| **Paiements**  | `payment.created`    | Une demande de paiement a été créée.                         |
|                | `payment.initiated`  | Le payeur a initié le flux de paiement.                      |
|                | `payment.executed`   | Le prestataire a confirmé le paiement (solde en attente).    |
|                | `payment.settled`    | Fonds compensés et disponibles sur le solde du portefeuille. |
|                | `payment.failed`     | Le paiement a échoué ou a été rejeté.                        |
|                | `payment.cancelled`  | La demande de paiement a été annulée.                        |
| **Retraits**   | `payout.created`     | Un retrait a été créé (en attente d'approbation).            |
|                | `payout.processing`  | Le retrait a été soumis au réseau de règlement.              |
|                | `payout.completed`   | Le retrait est confirmé comme effectué.                      |
|                | `payout.failed`      | Le retrait est confirmé comme échoué.                        |
|                | `payout.cancelled`   | Le retrait a été annulé par un administrateur.               |
| **Transferts** | `transfer.completed` | Transfert de registre interne effectué.                      |
|                | `transfer.failed`    | Transfert de registre interne échoué.                        |
| **Clients**    | `customer.created`   | Profil client créé.                                          |
|                | `customer.updated`   | Détails du profil client mis à jour.                         |

***

## Politique de Nouvelle Tentative & Anti-Rejeu

### Calendrier de Nouvelle Tentative avec Backoff Exponentiel

Si votre serveur renvoie un code d'erreur HTTP non-2xx ou ne répond pas dans les 30 secondes, les tentatives de livraison sont réitérées selon le calendrier suivant :

* **1 heure** après le premier échec
* **2 heures** après le deuxième échec
* **4 heures** après le troisième échec
* **8 heures** après le quatrième échec
* **16 heures** après le cinquième échec
* Les tentatives s'arrêtent après 5 échecs (le délai suivant dépassant 24 heures).

### Protection Anti-Rejeu et Déduplication

Chaque charge utile contient un identifiant d'événement `id` unique et un horodatage ISO `createdAt` dans son corps. Votre serveur peut enregistrer ces identifiants pour ignorer les livraisons en doublon.

***

## Tests et Renvois Manuels

* **Renvoi Manuel** : L'historique des livraisons est conservé. Vous pouvez réémettre manuellement un événement à l'aide de `POST /organizations/{id}/webhooks/{endpointId}/deliveries/{deliveryId}/resend`.
* **Tests de Signature** : Vous pouvez tester la vérification de signature à l'aide de vos clés de test ou dans vos suites de tests d'intégration.
