Webhooks
Recevoir les événements NovaFisko sur votre serveur, créer et tester un point de réception, comprendre les tentatives de livraison et consulter le journal.
Un webhook est un appel HTTP que NovaFisko envoie à votre serveur quand un événement se produit : une facture importée, une écriture validée, une déclaration TVA validée. Vous n'avez plus à interroger l'API pour savoir s'il y a du nouveau.
Les webhooks se configurent au niveau du cabinet et couvrent tous ses dossiers. Seuls les administrateurs et les gestionnaires du cabinet peuvent les gérer.
Fonctionnement
- Vous enregistrez une URL et choisissez les événements à recevoir.
- NovaFisko vous remet un secret de signature, une seule fois.
- À chaque événement, NovaFisko met une livraison en file, puis envoie une requête
POSTsignée à votre URL, au plus tard dans la minute. - Votre serveur vérifie la signature et répond par un statut
2xxen moins de 10 secondes. - En cas d'échec, NovaFisko réessaie avec un délai croissant.
Créer un point de réception
curl -X POST https://api.novafisko.com/v1/firms/Fd7hQ2mN8sXa/webhooks \
-H "Authorization: Bearer $NOVAFISKO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://erp.atelier.example/hooks/novafisko",
"description": "ERP Atelier, production",
"events": ["document.imported", "document.booked", "entry.posted"],
"is_active": true
}'
| Champ | Obligatoire | Description |
|---|---|---|
url |
oui | URL de réception, 500 caractères max. HTTPS obligatoire en production |
description |
non | Libellé libre, 190 caractères max |
events |
oui | Liste d'événements, ou ["*"] pour tous |
is_active |
non | true par défaut |
Réponse 201 :
{
"webhook": {
"id": 14,
"url": "https://erp.atelier.example/hooks/novafisko",
"description": "ERP Atelier, production",
"events": ["document.imported", "document.booked", "entry.posted"],
"is_active": true,
"secret_hint": "whsec_…x9Qd",
"last_delivery_at": null,
"last_status": null,
"created_at": "2026-10-05T09:20:00+00:00",
"updated_at": "2026-10-05T09:20:00+00:00"
},
"secret": "whsec_Zp3kV8nQ1bT6yR0mW4sH7cJ2dL9fA5gE1uXox9Qd"
}
Le secret n'est renvoyé qu'à la création. Enregistrez-le immédiatement dans votre gestionnaire de secrets. Les lectures suivantes ne montrent que secret_hint, ses quatre derniers caractères.
Contraintes sur l'URL
- Le schéma doit être
https. - L'URL ne doit pas contenir d'identifiants (
https://user:pass@...). - L'hôte ne doit pas être
localhost, ni se terminer par.localou.internal, ni pointer vers une adresse privée, de bouclage ou réservée. - Les redirections ne sont pas suivies.
Une URL refusée répond 422 avec le code webhook_url_refused. Un cabinet peut enregistrer 20 points de réception au maximum (webhook_limit_reached).
Gérer les points de réception
| Action | Requête | Réponse |
|---|---|---|
| Lister | GET /v1/firms/{firm}/webhooks |
{"data": [...]} |
| Modifier | PATCH /v1/firms/{firm}/webhooks/{id} |
{"webhook": {...}} |
| Supprimer | DELETE /v1/firms/{firm}/webhooks/{id} |
204 |
| Tester | POST /v1/firms/{firm}/webhooks/{id}/test |
202 et la livraison |
| Journal | GET /v1/firms/{firm}/webhooks/{id}/deliveries |
Liste paginée |
| Catalogue | GET /v1/webhooks/events |
Liste des événements et descriptions |
Mettre un point de réception en pause sans le supprimer :
curl -X PATCH https://api.novafisko.com/v1/firms/Fd7hQ2mN8sXa/webhooks/14 \
-H "Authorization: Bearer $NOVAFISKO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"is_active": false}'
Un utilisateur membre du cabinet sans le rôle requis reçoit 403. Un utilisateur extérieur au cabinet reçoit 404.
La requête reçue
POST /hooks/novafisko HTTP/1.1
Host: erp.atelier.example
Content-Type: application/json
User-Agent: NovaFisko-Webhooks/1.0
X-Novafisko-Event: entry.posted
X-Novafisko-Delivery: 90412
X-Novafisko-Signature: t=1791191400,v1=5f2b8c1e0a7d4f6b9c3e2a1d8f7b6c5a4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b
{"id":"evt_01jb6z3m8q2v7k4n5p9r0s1t2w","event":"entry.posted","api_version":"v1","created_at":"2026-10-05T09:23:20Z","firm":{"public_token":"Fd7hQ2mN8sXa","name":"Fiduciaire Exemple"},"company":{"public_token":"k3Jd9fPq2LmX","name":"Le Comptoir Montois SRL"},"data":{"id":1842,"reference":"ACH 2026/000212"}}
| En-tête | Contenu |
|---|---|
X-Novafisko-Signature |
Horodatage et signature HMAC-SHA256. Voir Vérifier les signatures |
X-Novafisko-Event |
Nom de l'événement |
X-Novafisko-Delivery |
Identifiant de la livraison, identique d'une tentative à l'autre |
User-Agent |
NovaFisko-Webhooks/1.0 |
Enveloppe
Tous les événements partagent la même enveloppe.
| Champ | Description |
|---|---|
id |
Identifiant unique de l'événement, préfixé evt_. Servez-vous-en pour dédoublonner |
event |
Nom de l'événement |
api_version |
v1 |
created_at |
Date de l'événement, ISO 8601 en UTC |
firm |
public_token et nom du cabinet |
company |
public_token et nom du dossier, ou null pour un événement de cabinet |
data |
Contenu propre à l'événement |
Événements
| Événement | Déclencheur |
|---|---|
document.imported |
Un document entre dans le dossier (importateur, Novadesko, Peppol) |
document.booked |
Un document est comptabilisé |
entry.posted |
Une écriture est validée |
entry.reversed |
Une écriture est extournée |
bank.transaction_imported |
Un mouvement bancaire est importé |
vat.declaration_validated |
Une déclaration TVA est validée |
fiscal_year.closed |
Un exercice est clôturé |
peppol.status_changed |
Le statut Peppol du dossier change |
third_party.created |
Un tiers est créé |
third_party.updated |
Un tiers est modifié |
webhook.test |
Envoi de test, à la demande |
Les exemples ci-dessous montrent le champ data de chaque événement. L'enveloppe est omise.
document.imported et document.booked
{
"id": 7741,
"source": "novafisko",
"origin": "novafisko",
"type": "purchases",
"direction": "purchase",
"is_credit_note": false,
"number": "F-2026-0918",
"document_date": "2026-10-02",
"due_date": "2026-11-01",
"third_party_id": 41,
"third_party_name": "Brasserie des Collines SA",
"third_party_vat": "BE0999900134",
"currency": "EUR",
"amount_net": "1250.00",
"amount_vat": "262.50",
"amount_gross": "1512.50",
"status": "pending",
"journal_entry_id": null
}
Pour document.booked, status vaut booked et journal_entry_id porte l'identifiant de l'écriture.
entry.posted et entry.reversed
{
"id": 1842,
"reference": "ACH 2026/000212",
"journal_code": "ACH",
"fiscal_year_code": "2026",
"number": 212,
"entry_date": "2026-10-02",
"due_date": "2026-11-01",
"label": "Brasserie des Collines SA",
"document_reference": "F-2026-0918",
"status": "posted",
"origin": "manual",
"third_party_id": 41,
"total_debit": "1512.50",
"total_credit": "1512.50",
"lines_count": 3,
"reversed_entry_id": null,
"reversed_by_entry_id": null
}
Pour entry.reversed, l'événement porte sur l'écriture d'origine : status vaut reversed et reversed_by_entry_id désigne l'extourne. L'extourne elle-même déclenche son propre entry.posted, avec reversed_entry_id renseigné. Les lignes ne sont pas incluses : lisez-les avec GET entries/{id}.
bank.transaction_imported
{
"id": 30418,
"source": "novadesko",
"account_iban": "BE68539007547034",
"value_date": "2026-10-03",
"amount": "-1512.50",
"currency": "EUR",
"counterpart_name": "Brasserie des Collines SA",
"counterpart_iban": "BE71096123456769",
"communication": "+++091/8202/60018+++",
"status": "pending"
}
vat.declaration_validated
{
"id": 88,
"period_type": "quarterly",
"year": 2026,
"period": 3,
"label": "3T 2026",
"starts_on": "2026-07-01",
"ends_on": "2026-09-30",
"amount_due": "4218.36",
"amount_refund": "0.00",
"status": "validated",
"validated_at": "2026-10-05T09:31:02+00:00",
"journal_entry_id": 1860
}
fiscal_year.closed
{
"id": 6,
"code": "2025",
"starts_on": "2025-01-01",
"ends_on": "2025-12-31",
"closed_at": "2026-03-14T10:22:05+00:00"
}
peppol.status_changed
{
"id": 57,
"participant_id": "0208:0999900134",
"full_id": "iso6523-actorid-upis::0208:0999900134",
"status": "active",
"previous_status": "pending",
"incoming_enabled": true,
"activated_at": "2026-10-05T09:02:11+00:00"
}
previous_status vaut null lors de la première inscription.
third_party.created et third_party.updated
{
"id": 215,
"type": "supplier",
"code": "MARBOR",
"name": "Maraîchers du Borinage SC",
"vat_number": "BE0999900233",
"enterprise_number": "0999.900.233",
"country": "BE",
"email": "info@maraichers-borinage.example",
"peppol_identifier": "0208:0999900233",
"peppol_registered": true,
"changed": ["email", "phone"]
}
Le champ changed, présent uniquement pour third_party.updated, liste les champs modifiés. Les vérifications techniques (VIES, Peppol) ne déclenchent pas d'événement à elles seules.
webhook.test
{
"message": "Test event sent from NovaFisko.",
"webhook_id": 14
}
Pour cet événement, company vaut null.
Tentatives et délais
Votre serveur doit répondre par un statut 2xx en moins de 10 secondes. Toute autre réponse, un dépassement de délai ou une erreur de connexion compte comme un échec.
| Tentative | Délai après l'échec précédent |
|---|---|
| 1 | Immédiate, dans la minute qui suit l'événement |
| 2 | 1 minute |
| 3 | 2 minutes |
| 4 | 4 minutes |
| 5 | 8 minutes |
| 6 | 16 minutes |
| 7 | 32 minutes |
| 8 | 64 minutes |
Après la huitième tentative, soit un peu plus de deux heures, la livraison passe au statut failed et n'est plus retentée. Un point de réception en échec n'est pas désactivé automatiquement : surveillez last_status.
Les livraisons partent d'un traitement planifié chaque minute. Un événement arrive donc avec un délai de quelques secondes à une minute. Les livraisons d'un point de réception mis en pause restent en attente et partent dès sa réactivation.
Tester
curl -X POST https://api.novafisko.com/v1/firms/Fd7hQ2mN8sXa/webhooks/14/test \
-H "Authorization: Bearer $NOVAFISKO_TOKEN"
L'événement webhook.test est envoyé immédiatement, sans nouvelle tentative en cas d'échec. La réponse est toujours 202 et contient la livraison avec le statut obtenu, delivered ou failed. La route est limitée à 12 tests par minute.
{
"delivery": {
"id": 90455,
"event_id": "evt_01jb6zb1t4c8h2m6q0x3y7z9a5",
"event": "webhook.test",
"status": "delivered",
"attempts": 1,
"next_attempt_at": null,
"response_status": 200,
"response_excerpt": "ok",
"duration_ms": 184,
"delivered_at": "2026-10-05T09:40:12+00:00",
"created_at": "2026-10-05T09:40:12+00:00",
"payload": {"id": "evt_01jb6zb1t4c8h2m6q0x3y7z9a5", "event": "webhook.test", "api_version": "v1", "created_at": "2026-10-05T09:40:12Z", "firm": {"public_token": "Fd7hQ2mN8sXa", "name": "Fiduciaire Exemple"}, "company": null, "data": {"message": "Test event sent from NovaFisko.", "webhook_id": 14}}
}
}
Journal des livraisons
curl "https://api.novafisko.com/v1/firms/Fd7hQ2mN8sXa/webhooks/14/deliveries?status=failed&per_page=25" \
-H "Authorization: Bearer $NOVAFISKO_TOKEN"
| Paramètre | Description |
|---|---|
status |
pending, delivered ou failed |
event |
Nom d'un événement |
per_page |
25 par défaut, 100 au maximum |
page |
Numéro de page |
{
"data": [
{
"id": 90412,
"event_id": "evt_01jb6z3m8q2v7k4n5p9r0s1t2w",
"event": "entry.posted",
"status": "failed",
"attempts": 8,
"next_attempt_at": null,
"response_status": 500,
"response_excerpt": "Internal Server Error",
"duration_ms": 412,
"delivered_at": null,
"created_at": "2026-10-05T09:23:20+00:00",
"payload": {"id": "evt_01jb6z3m8q2v7k4n5p9r0s1t2w", "event": "entry.posted"}
}
],
"meta": {"current_page": 1, "last_page": 1, "per_page": 25, "total": 1}
}
response_excerpt contient les 2000 premiers caractères de la réponse de votre serveur, ou le message d'erreur de connexion. payload est le corps exact qui a été envoyé : vous pouvez le rejouer vous-même après avoir corrigé une panne. Les livraisons terminées (delivered ou failed) sont conservées 30 jours.
Bonnes pratiques
- Vérifiez toujours la signature avant de traiter quoi que ce soit. Voir Vérifier les signatures.
- Répondez vite. Accusez réception avec un
200, placez l'événement dans une file et traitez-le ensuite. Un traitement long provoque des dépassements de délai et des doublons. - Dédoublonnez sur
id. Une même livraison peut vous parvenir plusieurs fois si votre réponse s'est perdue. Conservez lesidd'événements déjà traités. - Ne supposez aucun ordre. Deux événements proches peuvent arriver dans le désordre, surtout après une nouvelle tentative. Appuyez-vous sur
created_atet relisez la ressource en cas de doute. - Traitez l'événement comme un signal. Le contenu de
dataest un résumé. Pour l'état complet et à jour, appelez l'API. - Ignorez les événements inconnus. De nouveaux événements et de nouveaux champs peuvent apparaître sans changement de version. Répondez
200à ce que vous ne connaissez pas. - Prévoyez les rafales. Un import en masse (extrait CODA, synchronisation Novadesko) produit une livraison par document ou par mouvement.
- Combinez avec
sync/pull. Utilisez le webhook comme déclencheur et le flux de changements comme source de vérité. Voir Synchroniser en continu.
Le cabinet de démonstration n'émet aucun webhook et la gestion des points de réception y est bloquée (403, code demo_mode).