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

# Configuration des webhooks

> Guide pas-à-pas pour configurer, sécuriser et monitorer vos webhooks Finkare.

## Vue d'ensemble

Les webhooks Finkare vous notifient de chaque événement de recouvrement émis (paiement reçu, dossier payé, lancement, actions de relance, recommandés). Les payloads sont signés via HMAC-SHA256 pour garantir leur authenticité. La livraison est **au moins une fois** : dédupliquez sur l'identifiant de l'événement.

## Étape 1 : créer un endpoint

Créez un endpoint HTTPS sur votre serveur qui accepte les requêtes POST. La signature se vérifie sur le **corps brut** (ne pas re-sérialiser le JSON parsé) :

```typescript theme={null}
// Express.js
import express from 'express';
import crypto from 'crypto';

const app = express();

const TOLERANCE_SEC = 300;

// En-tête : t=<horodatage>,v1=<HMAC-SHA256(secret, "<t>.<corps brut>")>
function verifySignature(rawBody: Buffer, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > TOLERANCE_SEC) return false;
  const expected = Buffer.from(crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex'), 'hex');
  const received = Buffer.from(parts.v1 || '', 'hex');
  return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}

app.post('/webhooks/finkare', express.raw({ type: 'application/json', limit: '1mb' }), (req, res) => {
  // 1. Vérifier la signature (secret whsec_... reçu à la création)
  const header = (req.headers['x-finkare-signature'] as string) || '';
  if (!verifySignature(req.body, header, process.env.FINKARE_WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 2. Traiter l'événement (une seule fois par id)
  const { id, event, data } = JSON.parse(req.body.toString('utf8'));

  switch (event) {
    case 'payment.received':
      console.log(`Paiement reçu : ${data.amount} ${data.currency} pour le dossier ${data.debt_id}`);
      break;

    case 'debt.paid':
      console.log(`Dossier ${data.debt_id} soldé`);
      break;

    case 'workflow.action_triggered':
      console.log(`Action ${data.action} sur ${data.debt_id} via ${data.channel}`);
      break;

    default:
      console.log(`Événement ${event} (${id})`);
  }

  // 3. Répondre 200 rapidement
  res.status(200).json({ received: true });
});

app.listen(3000);
```

<Warning>
  Répondez toujours avec un code HTTP **2xx** dans les **5 secondes**. Si votre traitement est long, acquittez d'abord le webhook puis traitez en arrière-plan (queue). Les redirections ne sont jamais suivies : une réponse 3xx compte comme un échec.
</Warning>

## Étape 2 : enregistrer le webhook

```bash theme={null}
curl -X POST https://api.finkare.io/api/v1/webhooks \
  -H "X-API-Key: fk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.votre-entreprise.fr/webhooks/finkare",
    "events": [
      "payment.received",
      "debt.paid",
      "workflow.action_triggered",
      "lrar.delivered"
    ],
    "description": "Production : synchronisation ERP"
  }'
```

Sauvegardez le `secret` retourné dans vos variables d'environnement :

```bash theme={null}
export FINKARE_WEBHOOK_SECRET="whsec_a1b2c3d4e5f6..."
```

## Étape 3 : format des payloads

Chaque événement est envoyé avec cette structure :

```json theme={null}
{
  "id": "6f1c2d3e-4b5a-4c6d-8e7f-901234567890",
  "event": "payment.received",
  "created_at": "2026-10-09T14:22:00.000Z",
  "data": {
    "debt_id": "c3d4e5f6-a1b2-4890-8def-1234567890ab",
    "amount": 1500,
    "currency": "EUR",
    "provider": "mollie",
    "fully_paid": true,
    "total_paid": 1500,
    "remaining": 0
  }
}
```

Événements actuellement émis : `workflow.started`, `workflow.action_triggered`, `debtor.contacted`, `payment.received`, `debt.paid`, `lrar.sent`, `lrar.delivered`. Les autres événements de la [référence](/api-reference/webhooks) peuvent être souscrits mais ne sont pas encore émis. Les payloads ne contiennent pas de données personnelles du débiteur : identifiants, montants et états seulement.

### Headers envoyés

| Header | Description |
| - | - |
| `Content-Type` | `application/json` |
| `X-Finkare-Signature` | `t=<horodatage>,v1=<HMAC-SHA256(secret, "<t>.<corps brut>")>` |
| `X-Finkare-Event` | Type d'événement (ex : `payment.received`) |
| `X-Finkare-Event-Id` | Identifiant de l'événement (= `id` du corps), stable entre tentatives : clé de déduplication |
| `X-Finkare-Delivery-Id` | Identifiant de la livraison (un par webhook destinataire) |
| `X-Finkare-Timestamp` | Horodatage Unix de la tentative (identique au `t` signé) |

## Vérification de signature

En-tête : `X-Finkare-Signature: t=<horodatage Unix>,v1=<signature>`, où `signature` = HMAC-SHA256 hexadécimal, avec votre secret `whsec_...` comme clé, de la chaîne `"<t>.<corps brut>"`. L'horodatage étant signé, refusez toute requête dont `t` s'écarte de plus de **5 minutes** (300 s) de votre horloge : un corps intercepté ne peut pas être rejoué au-delà. Calculez toujours la signature sur le **corps brut**, avant tout parsing JSON. Finkare stocke votre secret chiffré et ne peut pas vous le renvoyer : en cas de perte, régénérez-le (`rotate-secret`).

<CodeGroup>
  ```typescript Node.js theme={null}
  import crypto from 'crypto';

  export function verifySignature(rawBody: Buffer, header: string, secret: string, toleranceSec = 300): boolean {
    const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
    const t = Number(parts.t);
    if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
    const expected = Buffer.from(crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex'), 'hex');
    const received = Buffer.from(parts.v1 || '', 'hex');
    return expected.length === received.length && crypto.timingSafeEqual(expected, received);
  }
  ```

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

  def verify_signature(raw_body: bytes, header: str, secret: str, tolerance_sec: int = 300) -> bool:
      parts = dict(p.split("=", 1) for p in (header or "").split(",") if "=" in p)
      try:
          t = int(parts.get("t", ""))
      except ValueError:
          return False
      if abs(time.time() - t) > tolerance_sec:
          return False
      expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, parts.get("v1", ""))
  ```
</CodeGroup>

## Politique de retry

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

Après 5 échecs, la livraison passe en `failed` (visible dans l'historique) ; le webhook reste actif pour les événements suivants. Une URL qui résout vers une adresse privée ou réservée est refusée sans nouvelle tentative.

## Monitoring

Consultez l'historique des deliveries via l'API :

```bash theme={null}
curl -H "X-API-Key: fk_live_xxx" \
  https://api.finkare.io/api/v1/webhooks/e5f6a7b8-c1d2-9012-ef01-345678901bcd/deliveries
```

## Bonnes pratiques

1. **Répondre vite** : acquittez le webhook en \< 5s, traitez en async
2. **Déduplication** : utilisez `X-Finkare-Event-Id` (ou `id` du corps) pour ignorer une re-livraison
3. **Idempotence** : votre handler doit supporter les re-livraisons sans effet de bord
4. **Rotation régulière** : faites tourner le secret via `POST /webhooks/:id/rotate-secret`
5. **Alerting** : surveillez les erreurs 4xx/5xx dans vos logs pour détecter les problèmes rapidement


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.