Skip to content
Documentation
English
Open the app

Webhooks

Receive NovaFisko events on your server, create and test an endpoint, understand delivery attempts and consult the log.

A webhook is an HTTP call that NovaFisko sends to your server when an event occurs: an invoice imported, an entry posted, a VAT return validated. You no longer have to poll the API to find out whether there is anything new.

Webhooks are configured at firm level and cover all its companies. Only the administrators and managers of the firm can manage them.

How it works

  1. You register a URL and choose the events to receive.
  2. NovaFisko gives you a signing secret, once only.
  3. At each event, NovaFisko queues a delivery, then sends a signed POST request to your URL, within the minute at the latest.
  4. Your server verifies the signature and responds with a 2xx status in less than 10 seconds.
  5. On failure, NovaFisko retries with an increasing delay.

Create an endpoint

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
  }'
Field Required Description
url yes Receiving URL, 500 characters max. HTTPS mandatory in production
description no Free label, 190 characters max
events yes List of events, or ["*"] for all
is_active no true by default

Response 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"
}
Warning

The secret is only returned at creation. Save it immediately in your secrets manager. Later reads only show secret_hint, its last four characters.

URL constraints

  • The scheme must be https.
  • The URL must not contain credentials (https://user:pass@...).
  • The host must not be localhost, nor end with .local or .internal, nor point to a private, loopback or reserved address.
  • Redirects are not followed.

A refused URL responds 422 with the webhook_url_refused code. A firm can register at most 20 endpoints (webhook_limit_reached).

Manage endpoints

Action Request Response
List GET /v1/firms/{firm}/webhooks {"data": [...]}
Update PATCH /v1/firms/{firm}/webhooks/{id} {"webhook": {...}}
Delete DELETE /v1/firms/{firm}/webhooks/{id} 204
Test POST /v1/firms/{firm}/webhooks/{id}/test 202 and the delivery
Log GET /v1/firms/{firm}/webhooks/{id}/deliveries Paginated list
Catalogue GET /v1/webhooks/events List of events and descriptions

Pausing an endpoint without deleting it:

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}'

A user who is a member of the firm without the required role receives 403. A user outside the firm receives 404.

The request received

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"}}
Header Content
X-Novafisko-Signature Timestamp and HMAC-SHA256 signature. See Verify signatures
X-Novafisko-Event Name of the event
X-Novafisko-Delivery Identifier of the delivery, identical from one attempt to the next
User-Agent NovaFisko-Webhooks/1.0

Envelope

All events share the same envelope.

Field Description
id Unique identifier of the event, prefixed with evt_. Use it to deduplicate
event Name of the event
api_version v1
created_at Date of the event, ISO 8601 in UTC
firm public_token and name of the firm
company public_token and name of the company, or null for a firm event
data Content specific to the event

Events

Event Trigger
document.imported A document enters the company (importer, Novadesko, Peppol)
document.booked A document is booked
entry.posted An entry is posted
entry.reversed An entry is reversed
bank.transaction_imported A bank transaction is imported
vat.declaration_validated A VAT return is validated
fiscal_year.closed A fiscal year is closed
peppol.status_changed The Peppol status of the company changes
third_party.created A third party is created
third_party.updated A third party is modified
webhook.test Test send, on demand

The examples below show the data field of each event. The envelope is omitted.

document.imported and 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
}

For document.booked, status is booked and journal_entry_id carries the identifier of the entry.

entry.posted and 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
}

For entry.reversed, the event concerns the original entry: status is reversed and reversed_by_entry_id points to the reversal. The reversal itself triggers its own entry.posted, with reversed_entry_id filled in. Lines are not included: read them with 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 is null at the first registration.

third_party.created and 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"]
}

The changed field, present only for third_party.updated, lists the modified fields. Technical checks (VIES, Peppol) do not trigger an event on their own.

webhook.test

{
  "message": "Test event sent from NovaFisko.",
  "webhook_id": 14
}

For this event, company is null.

Attempts and delays

Your server must respond with a 2xx status in less than 10 seconds. Any other response, a timeout or a connection error counts as a failure.

Attempt Delay after the previous failure
1 Immediate, within the minute following the event
2 1 minute
3 2 minutes
4 4 minutes
5 8 minutes
6 16 minutes
7 32 minutes
8 64 minutes

After the eighth attempt, that is a little over two hours, the delivery moves to the failed status and is no longer retried. A failing endpoint is not disabled automatically: monitor last_status.

Note

Deliveries are sent by a scheduled job every minute. An event therefore arrives with a delay of a few seconds to one minute. The deliveries of a paused endpoint remain pending and are sent as soon as it is reactivated.

Testing

curl -X POST https://api.novafisko.com/v1/firms/Fd7hQ2mN8sXa/webhooks/14/test \
  -H "Authorization: Bearer $NOVAFISKO_TOKEN"

The webhook.test event is sent immediately, with no retry on failure. The response is always 202 and contains the delivery with the resulting status, delivered or failed. The route is limited to 12 tests per 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}}
  }
}

Delivery log

curl "https://api.novafisko.com/v1/firms/Fd7hQ2mN8sXa/webhooks/14/deliveries?status=failed&per_page=25" \
  -H "Authorization: Bearer $NOVAFISKO_TOKEN"
Parameter Description
status pending, delivered or failed
event Name of an event
per_page 25 by default, 100 at most
page Page number
{
  "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 contains the first 2000 characters of your server's response, or the connection error message. payload is the exact body that was sent: you can replay it yourself after fixing an outage. Finished deliveries (delivered or failed) are kept for 30 days.

Best practices

  • Always verify the signature before processing anything. See Verify signatures.
  • Respond quickly. Acknowledge receipt with a 200, put the event in a queue and process it afterwards. Long processing causes timeouts and duplicates.
  • Deduplicate on id. The same delivery may reach you several times if your response was lost. Keep the id values of events already processed.
  • Assume no order. Two events close together may arrive out of order, especially after a retry. Rely on created_at and read the resource again when in doubt.
  • Treat the event as a signal. The content of data is a summary. For the complete and current state, call the API.
  • Ignore unknown events. New events and new fields may appear without a version change. Respond 200 to anything you do not know.
  • Plan for bursts. A bulk import (CODA statement, Novadesko synchronisation) produces one delivery per document or per transaction.
  • Combine with sync/pull. Use the webhook as a trigger and the change stream as the source of truth. See Continuous sync.
Warning

The demo firm emits no webhooks and endpoint management is blocked there (403, code demo_mode).

See also