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

# Webhooks

> Configuration des webhooks pour recevoir des notifications en temps réel sur les événements de recouvrement.

<Info>**Tester en direct** — Essayez ces endpoints dans la [documentation interactive Scalar](https://api.finkare.io/docs).</Info>

## Lister les webhooks

**Scope requis :** `webhooks:manage`

```bash theme={null}
curl -H "X-API-Key: fk_live_xxx" \
  https://api.finkare.io/api/v1/webhooks
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "e5f6a7b8-c1d2-9012-ef01-345678901bcd",
      "url": "https://api.acme.fr/webhooks/finkare",
      "events": ["payment.received", "invoice.status_changed"],
      "description": "Webhook production ACME",
      "enabled": true,
      "created_at": "2026-03-01T10:00:00Z",
      "last_triggered_at": "2026-04-07T18:30:00Z"
    }
  ],
  "requestId": "req_wh_list_01",
  "timestamp": "2026-04-08T10:00:00Z"
}
```

***

## Créer un webhook

**Scope requis :** `webhooks:manage`

<ParamField body="url" type="string" required>
  URL de destination (HTTPS obligatoire)
</ParamField>

<ParamField body="events" type="array" required>
  Événements à écouter (voir tableau ci-dessous)
</ParamField>

<ParamField body="description" type="string" optional>
  Description du webhook
</ParamField>

<ParamField body="enabled" type="boolean" optional default="true">
  Activer ou désactiver le webhook
</ParamField>

```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",
      "invoice.status_changed",
      "workflow.action_triggered"
    ],
    "description": "Webhook principal production"
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "e5f6a7b8-c1d2-9012-ef01-345678901bcd",
    "url": "https://api.votre-entreprise.fr/webhooks/finkare",
    "events": ["payment.received", "invoice.status_changed", "workflow.action_triggered"],
    "enabled": true,
    "secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
    "createdAt": "2026-04-08T10:00:00Z"
  },
  "message": "Webhook created. Save the secret - it will not be shown again.",
  "requestId": "req_wh_create_01",
  "timestamp": "2026-04-08T10:00:00Z"
}
```

<Warning>
  Le **secret de signature** (`whsec_...`) est retourné **une seule fois**. Sauvegardez-le immédiatement. Il est utilisé pour vérifier l'authenticité des payloads via HMAC-SHA256.
</Warning>

***

## Événements disponibles

| Événement | Description |
| - | - |
| `invoice.created` | Nouvelle facture créée ou importée |
| `invoice.updated` | Facture mise à jour (metadata/notes) |
| `invoice.status_changed` | Changement de statut d'une facture |
| `payment.received` | Paiement reçu (total ou partiel) |
| `payment.failed` | Échec de paiement |
| `workflow.started` | Workflow de recouvrement démarré |
| `workflow.action_triggered` | Action de recouvrement déclenchée (email, SMS, appel...) |
| `workflow.completed` | Workflow terminé (payé ou clôturé) |
| `lrar.sent` | LRAR envoyée |
| `lrar.delivered` | LRAR distribuée (AR reçu) |
| `debtor.contacted` | Débiteur contacté (email, SMS, appel) |
| `debt.paid` | Dossier marqué comme payé (preuve validée) |
| `cascade.action` | Action de cascade exécutée par Bob (pause, accélération) |
| `judicial.started` | Procédure judiciaire initiée |
| `anr.created` | Avis de non-recouvrement émis après validation du créancier (identifiants, numéro, version, motif, reste dû ; aucune donnée personnelle) |

***

## Récupérer un webhook

**Scope requis :** `webhooks:manage`

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

***

## Mettre à jour un webhook

**Scope requis :** `webhooks:manage`

```bash theme={null}
curl -X PUT https://api.finkare.io/api/v1/webhooks/e5f6a7b8-c1d2-9012-ef01-345678901bcd \
  -H "X-API-Key: fk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["payment.received", "invoice.status_changed", "debt.paid"],
    "description": "Webhook v2 — ajout événement debt.paid"
  }'
```

***

## Supprimer un webhook

**Scope requis :** `webhooks:manage`

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

Retourne `204 No Content`.

***

## Régénérer le secret

Génère un nouveau secret et invalide l'ancien.

**Scope requis :** `webhooks:manage`

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

```json theme={null}
{
  "success": true,
  "data": {
    "secret": "whsec_nouveau_secret_genere_ici_abc123def456"
  },
  "message": "Secret rotated. Save the new secret - it will not be shown again."
}
```

***

## Historique des deliveries

Consultez les tentatives de livraison (succès et échecs) pour un webhook.

**Scope requis :** `webhooks:manage`

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

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "del_001",
      "eventType": "payment.received",
      "status": "delivered",
      "attempts": 1,
      "responseStatus": 200,
      "lastAttemptAt": "2026-04-07T18:30:00Z",
      "createdAt": "2026-04-07T18:30:00Z"
    },
    {
      "id": "del_002",
      "eventType": "invoice.status_changed",
      "status": "failed",
      "attempts": 3,
      "responseStatus": 500,
      "lastAttemptAt": "2026-04-07T19:15:00Z",
      "nextRetryAt": "2026-04-07T19:45:00Z",
      "createdAt": "2026-04-07T19:00:00Z"
    }
  ],
  "pagination": { "total": 2, "limit": 20, "offset": 0 }
}
```

### Politique de retry

En cas d'échec de livraison (timeout de 5 secondes, erreur réseau, code HTTP hors 2xx, y compris une redirection 3xx qui n'est jamais suivie), Finkare retente automatiquement avec un délai croissant :

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

Après 5 tentatives échouées, 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.

La livraison est **au moins une fois** : un même événement peut exceptionnellement arriver deux fois. Dédupliquez avec l'en-tête `X-Finkare-Event-Id` (identique d'une tentative à l'autre).

***

## Format du corps

```json theme={null}
{
  "id": "6f1c2d3e-4b5a-4c6d-8e7f-901234567890",
  "event": "payment.received",
  "created_at": "2026-10-09T10:00:00.000Z",
  "data": { "debt_id": "...", "amount": 120.5, "currency": "EUR", "provider": "mollie" }
}
```

En-têtes : `X-Finkare-Event`, `X-Finkare-Event-Id`, `X-Finkare-Delivery-Id`, `X-Finkare-Timestamp`, `X-Finkare-Signature` (`t=<horodatage>,v1=<signature>`, voir ci-dessous).

Événements actuellement émis : `workflow.started`, `workflow.action_triggered`, `debtor.contacted`, `payment.received`, `debt.paid`, `lrar.sent`, `lrar.delivered`. Les autres événements du tableau peuvent être souscrits mais ne sont pas encore émis.

***

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

<Tip>
  Utilisez toujours `timingSafeEqual` (ou équivalent) pour la comparaison des signatures afin d'éviter les attaques timing.
</Tip>


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