Build1 publisher3 min readPublished
TimeTracker routes Belgian Peppol invoices through a sidecar bridge to certified Access Points
TimeTracker, a self-hosted invoicing app, meets Belgium's 2026 Peppol mandate by POSTing UBL invoices to a bridge that forwards them to certified Access Points. The self-hosted app handles a small HTTP contract and a provider account, and the certified provider handles signatures and receipts.
The Engineer · Build desk

What happened
- Since 1 January 2026, Belgian businesses must exchange B2B invoices as structured e-invoices over Peppol, and a PDF sent by email no longer counts, according to TimeTracker's maintainer.
- The recommended path has TimeTracker POST the UBL invoice and routing metadata to an adapter URL that forwards it to the user's own certified Access Point provider.
- The repo ships a peppol-bridge service for Docker Compose, with presets for the einvoice and peppyrus providers plus a generic_custom option.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A self-hosted invoicing tool with Belgian B2B users can no longer treat PDF export as finished work; its users cannot bill compliantly until it emits UBL and has a route onto Peppol.
- decision Every TimeTracker deployment now has to choose a certified Access Point provider and supply its API key, because the bridge only forwards invoices and never delivers them itself.
- cost Supporting both Peppol and Factur-X commits the maintainer to keeping two XML serializations, UBL and CII, in step for the same invoice data.
- capability A user can switch Access Point providers by changing a bridge preset, and the invoicing code and its stored send history stay as they are.
"Peppol is the pipe, UBL is what goes through it, and Factur-X is a separate format that happens to share the same underlying standard," TimeTracker's maintainer wrote [3]. That standard is EN 16931, which the post names in its title [17]. Peppol uses UBL and Factur-X uses CII, so TimeTracker generates both XML formats from the same invoice [4]. The description of the Belgian rule is the maintainer's own. The post says it is not legal or tax advice and tells readers to check the official guidance from the Belgian FPS Finance [16].
A sender reaches Peppol through an Access Point [5]. To speak the protocol directly, you look the receiver up in the SML/SMP directory, then deliver over AS4 with certificates, signatures and receipts [5]. TimeTracker's native mode does the lookup and the AS4 send, and that is enough for testing [6]. It lacks WS-Security, digital signatures and receipt handling, and the post calls certification "a business in itself" [6]. Those three missing features are all part of the transport. "For a self-hosted app used by freelancers, that's the wrong layer to own," the maintainer wrote [7]. For freelancers running their own server, I think that call is right.
The recommended path is a plain HTTP POST [8]. TimeTracker builds the UBL and sends it, with routing metadata, to an adapter URL. The adapter forwards it to the certified Access Point provider the user already has [8]. The JSON has four parts. Recipient and sender are each an `endpoint_id` plus a `scheme_id`. A document block holds the invoice ID, a UBL Invoice 2.1 `type_id` and the process ID `urn:fdc:peppol.eu:2017:poacc:billing:01:1.0`. The payload is the UBL XML as a string [9]. The adapter replies with a `message_id` [10]. This part is well designed. The failure rule is one comparison: any HTTP status of 400 or higher marks the attempt as failed. Every attempt is stored as pending, then sent or failed, so the invoice page can show the full history [10].
"Asking self-hosters to write their own adapter would kill adoption," the maintainer wrote [11]. So the repo ships `peppol-bridge`, a service that runs beside the app in Docker Compose [12]. The Compose file marks both secrets as required, in the form `PEPPOL_BRIDGE_AUTH_TOKEN=${PEPPOL_BRIDGE_AUTH_TOKEN:?Set PEPPOL_BRIDGE_AUTH_TOKEN}`. `PEPPOL_BRIDGE_PROVIDER` takes `einvoice`, `peppyrus` or `generic_custom` [12]. The bridge exposes `/health`, `/test` for checking provider credentials, and `/send` [13]. Differences between providers stay inside the bridge. One provider authenticates with an `X-Api-Key` header instead of `Authorization: Bearer`, and the app never needs to know [13]. An admin wizard writes the Compose snippet and points the app at `http://peppol-bridge:8088/send` [14]. Adding a provider means adding a preset to the bridge. The invoicing code does not change [14].
Another self-hosted tool can copy this design only if its users have an account with a certified Access Point provider, because the bridge forwards invoices and does not deliver them itself [8]. The identifiers also have to be correct. Every Peppol party is a scheme ID plus an endpoint ID, and Belgian companies typically use scheme 0208 [15]. The post does not say how the UBL output was validated against Peppol's rules, so it cannot show whether building the file was the easy part. It does show that a single invoice needed two XML serializations [4].
What to watch
- Whether TimeTracker's native mode gains WS-Security, digital signatures and receipt handling, or stays a test-only path.
- New provider presets in peppol-bridge beyond einvoice, peppyrus and generic_custom.
- FPS Finance guidance on the Belgian mandate, which the maintainer tells readers to check before relying on the post.