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
- Allez dans Configuration > Webhooks de votre compte Frihet
- Cliquez sur Créer un webhook
- Saisissez un nom descriptif, l'URL de destination et sélectionnez les événements que vous souhaitez recevoir
- Définissez éventuellement un secret pour la vérification HMAC (recommandé)
- 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
| Champ | Type | Requis | Description |
|---|---|---|---|
url | string | Oui | URL de destination (HTTPS obligatoire en production) |
events | string[] | Oui | Liste des types d'événements à recevoir |
secret | string | Non | Secret pour la signature HMAC-SHA256 (recommandé) |
active | boolean | Non | É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
localhostet127.0.0.1pendant 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énement | Description |
|---|---|
invoice.created | Une nouvelle facture a été créée |
invoice.updated | Une facture existante a été modifiée |
invoice.paid | Une facture a été marquée comme payée |
invoice.overdue | Une facture a dépassé sa date d'échéance |
Dépenses (2 événements)
| Événement | Description |
|---|---|
expense.created | Une nouvelle dépense a été enregistrée |
expense.updated | Une dépense existante a été modifiée |
Devis (4 événements)
| Événement | Description |
|---|---|
quote.created | Un nouveau devis a été créé |
quote.updated | Un devis existant a été modifié |
quote.accepted | Un client a accepté un devis (changement de statut à accepted) |
quote.rejected | Un client a refusé un devis (changement de statut à rejected) |
Clients (2 événements)
| Événement | Description |
|---|---|
client.created | Un nouveau client a été enregistré |
client.updated | Les données d'un client ont été modifiées |
Produits (2 événements)
| Événement | Description |
|---|---|
product.created | Un nouveau produit ou service a été créé |
product.updated | Un 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ête | Description | Exemple |
|---|---|---|
Content-Type | Type de contenu | application/json |
X-Frihet-Event | Type d'événement | invoice.paid |
X-Frihet-Delivery-Id | Identifiant unique de la livraison | d4e5f6a7b8c9 |
X-Frihet-Timestamp | Horodatage ISO 8601 | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | Signature HMAC-SHA256 du payload | sha256=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 à :
- Prendre le corps brut de la requête (raw body)
- Calculer le HMAC-SHA256 en utilisant le secret comme clé
- Comparer le résultat avec la valeur de l'en-tête
X-Frihet-Signature(sans le préfixesha256=)
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 :
| Tentative | Délai | Temps cumulé |
|---|---|---|
| 1 (initial) | immédiat | 0s |
| 2 (première tentative) | 2 secondes | 2s |
| 3 (deuxième tentative) | 4 secondes | 6s |
- 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 :
- Allez dans Paramètres > Développeurs > Webhooks
- Cliquez sur le webhook que vous souhaitez inspecter
- 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,retryingoufailed
Problèmes courants
| Symptôme | Cause probable | Solution |
|---|---|---|
| Toutes les livraisons échouent avec un timeout | L'endpoint met plus de 30s à répondre | Acceptez l'événement avec un 200 immédiat et traitez-le de manière asynchrone |
| Erreur SSL/TLS | Certificat expiré ou chaîne incomplète | Renouvelez le certificat et vérifiez la chaîne avec openssl s_client |
| Code 403 systématique | Pare-feu bloquant les requêtes POST entrantes | Autorisez le trafic depuis les IPs de Google Cloud (us-central1) |
| Code 502/503 | Serveur tombé en panne ou en maintenance | Vé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
- Vérifiez que l'URL du webhook est correcte et accessible depuis internet
- Vérifiez que le webhook est en état actif dans le tableau de bord
- Consultez les logs de livraison pour voir s'il y a des erreurs
- 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
- Vérifiez que vous utilisez le corps brut de la requête (avant d'analyser le JSON)
- Confirmez que le secret dans votre code correspond à celui configuré dans le tableau de bord Frihet
- Ne modifiez ni ne reformatez le corps avant de vérifier la signature
- 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
- 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.
- Consultez l'historique des livraisons pour confirmer l'état de chaque tentative.
- 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.