Aller au contenu principal

Webhooks

Les webhooks Frihet permettent de recevoir des notifications HTTP en temps réel lorsque des événements se produisent dans votre compte. Au lieu d'interroger l'API, configurez une URL et Frihet enverra une requête POST avec les données de l'événement au moment où il se produit.

Configuration

Vous pouvez gérer les webhooks depuis le tableau de bord Frihet (Configuration > Webhooks) ou de manière programmatique via l'API REST.

Depuis le tableau de bord

  1. Allez dans Configuration > Webhooks de votre compte Frihet
  2. Cliquez sur Créer un webhook
  3. Saisissez un nom descriptif, l'URL de destination et sélectionnez les événements que vous souhaitez recevoir
  4. Définissez éventuellement un secret pour la vérification HMAC (recommandé)
  5. Enregistrez le webhook

API REST de webhooks

Gérez les webhooks de manière programmatique avec les endpoints CRUD complets.

Lister les webhooks

GET /v1/webhooks
curl https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui"

Réponse (200) :

{
"data": [
{
"id": "wh_abc123",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "invoice.created"],
"active": true,
"createdAt": "2026-02-01T10:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}

Obtenir un webhook

GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"

Créer un webhook

POST /v1/webhooks
ChampTypeRequisDescription
urlstringOuiURL de destination (HTTPS obligatoire en production)
eventsstring[]OuiListe des types d'événements à recevoir
secretstringNonSecret pour la signature HMAC-SHA256 (recommandé)
activebooleanNonÉtat du webhook (par défaut : true)
curl -X POST https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"secret": "mi-secreto-hmac"
}'

Réponse (201) :

{
"id": "wh_def456",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"active": true,
"createdAt": "2026-03-18T10:00:00.000Z"
}

Mettre à jour un webhook

PATCH /v1/webhooks/:id

Vous n'avez besoin d'envoyer que les champs que vous souhaitez modifier.

curl -X PATCH https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"events": ["invoice.paid", "invoice.created", "expense.created"],
"active": false
}'

Réponse (200) : Objet webhook mis à jour.

Supprimer un webhook

DELETE /v1/webhooks/:id
curl -X DELETE https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui"

Réponse : 204 No Content


Exigences de l'URL :

  • Doit utiliser HTTPS (HTTP n'est autorisé que pour localhost et 127.0.0.1 pendant le développement)
  • Les IPs privées (10.x, 172.16-31.x, 192.168.x) ne sont pas autorisées pour éviter les attaques SSRF
  • Doit répondre avec un code 2xx en moins de 30 secondes

Limites :

  • Maximum 20 webhooks par compte
  • Payload maximum de 100 KB par livraison

Types d'événements

Frihet émet 14 types d'événements, regroupés par ressource.

Factures (4 événements)

ÉvénementDescription
invoice.createdUne nouvelle facture a été créée
invoice.updatedUne facture existante a été modifiée
invoice.paidUne facture a été marquée comme payée
invoice.overdueUne facture a dépassé sa date d'échéance

Dépenses (2 événements)

ÉvénementDescription
expense.createdUne nouvelle dépense a été enregistrée
expense.updatedUne dépense existante a été modifiée

Devis (4 événements)

ÉvénementDescription
quote.createdUn nouveau devis a été créé
quote.updatedUn devis existant a été modifié
quote.acceptedUn client a accepté un devis (changement de statut à accepted)
quote.rejectedUn client a refusé un devis (changement de statut à rejected)

Clients (2 événements)

ÉvénementDescription
client.createdUn nouveau client a été enregistré
client.updatedLes données d'un client ont été modifiées

Produits (2 événements)

ÉvénementDescription
product.createdUn nouveau produit ou service a été créé
product.updatedUn produit ou service existant a été modifié

Structure du payload

Chaque livraison de webhook est une requête POST avec un corps JSON. La structure est la même pour tous les types d'événements :

{
"event": "invoice.paid",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"id": "inv_abc123",
"clientName": "Acme S.L.",
"items": [
{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }
],
"total": 750,
"status": "paid",
"paidAt": "2026-02-12T14:29:58.000Z",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-12T14:30:00.000Z"
}
}

Le champ data contient l'état complet de la ressource au moment de l'événement.


En-têtes de livraison

Chaque requête de webhook inclut les en-têtes suivants :

En-têteDescriptionExemple
Content-TypeType de contenuapplication/json
X-Frihet-EventType d'événementinvoice.paid
X-Frihet-Delivery-IdIdentifiant unique de la livraisond4e5f6a7b8c9
X-Frihet-TimestampHorodatage ISO 86012026-02-12T14:30:00.000Z
X-Frihet-SignatureSignature HMAC-SHA256 du payloadsha256=a1b2c3d4e5f6...

L'en-tête X-Frihet-Signature n'est inclus que si vous avez configuré un secret dans le webhook.


Vérification de signature

Si vous configurez un secret lors de la création du webhook, Frihet signe chaque payload avec HMAC-SHA256. Le processus de vérification consiste à :

  1. Prendre le corps brut de la requête (raw body)
  2. Calculer le HMAC-SHA256 en utilisant le secret comme clé
  3. Comparer le résultat avec la valeur de l'en-tête X-Frihet-Signature (sans le préfixe sha256=)

Avec le SDK (recommandé)

import { Webhooks } from '@frihet/sdk';

app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = Webhooks.verifySignature(
req.body,
req.headers['x-frihet-signature'],
process.env.FRIHET_WEBHOOK_SECRET,
);

if (!isValid) return res.status(401).send('Signature invalide');

const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});

Exemple manuel en Node.js

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');

const receivedSignature = signature.replace('sha256=', '');

return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}

// Usage dans un serveur Express
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-frihet-signature'];
const secret = process.env.FRIHET_WEBHOOK_SECRET;

if (!signature || !verifyWebhookSignature(req.body.toString(), signature, secret)) {
return res.status(401).send('Signature invalide');
}

const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];

console.log(`Événement reçu : ${eventType}`, event.data);

// Traiter l'événement selon son type
switch (eventType) {
case 'invoice.paid':
// Mettre à jour votre système de comptabilité
break;
case 'expense.created':
// Notifier l'équipe financière
break;
}

res.status(200).send('OK');
});

Exemple en Python

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = 'tu-secreto-aqui'

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()

received = signature.replace('sha256=', '')

return hmac.compare_digest(expected, received)

@app.route('/webhook/frihet', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Frihet-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(401)

event = request.get_json()
event_type = request.headers.get('X-Frihet-Event')

print(f'Événement reçu : {event_type}')

if event_type == 'invoice.paid':
# Mettre à jour votre système de comptabilité
pass
elif event_type == 'quote.accepted':
# Convertir le devis en facture
pass

return 'OK', 200

Important : Utilisez toujours une comparaison à temps constant (timingSafeEqual en Node.js, compare_digest en Python) pour éviter les attaques par chronométrage.


Politique de tentatives de renvoi

Si la livraison d'un webhook échoue (code de réponse non-2xx ou délai d'attente), Frihet réessaie automatiquement avec une temporisation exponentielle :

TentativeDélaiTemps cumulé
1 (initial)immédiat0s
2 (première tentative)2 secondes2s
3 (deuxième tentative)4 secondes6s
  • Maximum 3 tentatives par livraison (1 initiale + 2 tentatives de renvoi)
  • Délai maximum de 30 secondes entre les tentatives
  • Délai d'expiration de 30 secondes par requête
  • Si les 3 tentatives échouent, la livraison est marquée comme failed

Les tentatives sont traitées par un job qui s'exécute toutes les 5 minutes, garantissant la fiabilité même si le processus principal redémarre.

Vous pouvez consulter l'historique des livraisons de chaque webhook depuis le tableau de bord Frihet, y compris le code de réponse, le corps de la réponse (premiers 1000 caractères) et les erreurs de chaque tentative.


Tests

Depuis le tableau de bord Frihet, vous pouvez envoyer un événement de test à tout webhook configuré. Le payload de test a la structure suivante :

{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}

Cela permet de vérifier que votre endpoint est accessible, que la signature est correctement validée et que votre système traite les événements sans erreurs.


Bonnes pratiques

Répondez rapidement

Votre endpoint doit répondre avec un code 200 le plus rapidement possible. Si vous devez effectuer un traitement lourd (envoyer des e-mails, mettre à jour des bases de données externes, etc.), acceptez l'événement et traitez-le de manière asynchrone dans une file d'attente de tâches.

Gérez l'idempotence

Il est possible qu'un même événement soit livré plus d'une fois (par exemple, si votre serveur a répondu avec un délai d'attente mais a traité l'événement). Utilisez le champ X-Frihet-Delivery-Id comme clé d'idempotence pour éviter les doublons.

Vérifiez toujours la signature

Ne faites jamais confiance à un webhook sans vérifier l'en-tête X-Frihet-Signature. Tout acteur ayant accès à votre URL pourrait envoyer de faux payloads.

Utilisez HTTPS

En production, votre endpoint doit être protégé par HTTPS. Frihet rejette les URLs HTTP (sauf en développement local).

Surveillez les échecs

Vérifiez régulièrement les logs de livraison dans le tableau de bord Frihet. Si vous constatez des livraisons échouées de manière récurrente, vérifiez que votre endpoint est disponible et répond en moins de 30 secondes.

Filtrez les événements

Abonnez-vous uniquement aux événements dont vous avez besoin. Chaque webhook peut écouter un ou plusieurs types d'événements. Moins vous traitez d'événements inutiles, moins la charge sur votre serveur sera élevée.


Débogage des livraisons

Frihet enregistre le résultat de chaque livraison de webhook. Vous pouvez le consulter depuis le tableau de bord :

  1. Allez dans Paramètres > Développeurs > Webhooks
  2. Cliquez sur le webhook que vous souhaitez inspecter
  3. Ouvrez l'onglet Livraisons

Chaque entrée affiche :

  • Code de réponse HTTP renvoyé par votre endpoint
  • Temps de réponse en millisecondes
  • Corps de la réponse (premiers 1000 caractères)
  • Date et heure de chaque tentative (y compris les tentatives de renvoi)
  • Statut final : delivered, retrying ou failed

Problèmes courants

SymptômeCause probableSolution
Toutes les livraisons échouent avec un timeoutL'endpoint met plus de 30s à répondreAcceptez l'événement avec un 200 immédiat et traitez-le de manière asynchrone
Erreur SSL/TLSCertificat expiré ou chaîne incomplèteRenouvelez le certificat et vérifiez la chaîne avec openssl s_client
Code 403 systématiquePare-feu bloquant les requêtes POST entrantesAutorisez le trafic depuis les IPs de Google Cloud (us-central1)
Code 502/503Serveur tombé en panne ou en maintenanceVérifiez les logs de votre serveur et assurez-vous que le processus est actif

Bouton de test

Sur l'écran de détail de chaque webhook, le bouton Envoyer un test envoie un événement synthétique webhook.test à votre URL. Utilisez ce bouton pour vérifier :

  • Que l'URL est accessible depuis internet
  • Que la signature HMAC est correctement validée
  • Que votre serveur répond avec un code 2xx

Le résultat du test apparaît immédiatement dans le log des livraisons.


Résolution des problèmes

Je ne reçois pas de webhooks

  1. Vérifiez que l'URL du webhook est correcte et accessible depuis internet
  2. Vérifiez que le webhook est en état actif dans le tableau de bord
  3. Consultez les logs de livraison pour voir s'il y a des erreurs
  4. Si vous utilisez un pare-feu, assurez-vous que les requêtes POST entrantes depuis Google Cloud (us-central1) sont autorisées

Les signatures ne correspondent pas

  1. Vérifiez que vous utilisez le corps brut de la requête (avant d'analyser le JSON)
  2. Confirmez que le secret dans votre code correspond à celui configuré dans le tableau de bord Frihet
  3. Ne modifiez ni ne reformatez le corps avant de vérifier la signature
  4. Vérifiez que votre framework n'analyse pas automatiquement le corps avant que vous puissiez accéder au brut

Les tentatives de renvoi n'arrivent pas

  1. Les tentatives de renvoi sont traitées toutes les 5 minutes. Si votre endpoint est tombé en panne brièvement, il est possible que les tentatives soient déjà épuisées.
  2. Consultez l'historique des livraisons pour confirmer l'état de chaque tentative.
  3. Si les 3 tentatives ont échoué, la livraison ne sera pas automatiquement réessayée.

Payload trop volumineux

Si la ressource associée à l'événement est très volumineuse (nombreuses lignes dans une facture, champs étendus), le payload peut dépasser la limite de 100 KB et la livraison sera rejetée. Simplifiez les données de la ressource ou contactez le support si vous avez besoin d'une limite supérieure.