Zwegers IT
Home/Projecten/Facturatie
Case study · .NET & Azure

Facturatie Zwegers IT — automatische facturen, boekhouding en incasso

Gijs Zwegers18 augustus 2026Leestijd ~8 min
dotnetazuresnelstartmollie

Voor Zwegers IT zelf heb ik een systeem gebouwd dat elke maand automatisch de abonnementen berekent, de factuur-PDF maakt, die naar de klant mailt, de verkoop boekt in mijn boekhoudpakket SnelStart, en het geld via automatische incasso ophaalt bij Mollie. Geen spreadsheet meer, geen los PDF-sjabloon, geen “heb ik deze klant deze maand al gefactureerd?” — en, belangrijker nog, geen moment waarop een typefout zomaar geld van iemands rekening haalt.

Het probleem: SnelStart kan geen facturen versturen

SnelStart is uitstekend in boekhouden, maar de B2B-API heeft geen endpoint om een factuur te renderen of te mailen — die bestaat gewoon niet. De meeste facturatietools lossen dat op door om SnelStart heen te bouwen. Ik heb het andersom gedaan: de app maakt de PDF zelf, verstuurt de mail zelf, en gebruikt SnelStart precies waar het goed in is. Elke factuur die de deur uitgaat, staat als verkoopboeking in SnelStart — niet als schaduwboekhouding ernaast, maar als de bron van waarheid voor de administratie.

Hoe een maand verloopt

Facturatie is precies het soort proces waar “gewoon direct uitvoeren” op de lange termijn een keer misgaat. Daarom is berekenen en uitvoeren bewust twee gescheiden stappen, met een nacht ertussen:

MomentWat er gebeurt
1e van de maand, 07:00De planner berekent conceptfacturen voor alle abonnementen. Er wordt nog niets geboekt of verstuurd.
binnen 24 uurIk kan de concepten bekijken. Overschrijdt de run een veiligheidsgrens, dan stopt hij vanzelf en krijg ik een mail.
+ 24 uurBoeken in SnelStart → PDF genereren → mail naar de klant.
6e van de maandDe incasso wordt aangeboden bij Mollie (verplichte SEPA-prenotificatietermijn).
doorlopendEen Mollie-webhook meldt betaald, mislukt of gestorneerd. Herinneringen gaan automatisch op +3, +10 en +21 dagen na de vervaldatum.

Die 24 uur is de belangrijkste ontwerpkeuze in de hele app. Een verkeerde prijs of een dubbel geïmporteerd abonnement zie je in de conceptfase nog gewoon; ná het boeken en incasseren niet meer. Crediteren en teruggeboekte incasso’s zijn een stuk vervelender dan één dag wachten. Wil ik zelf sneller, dan zet ik het goedkeuringsvenster op nul — maar de standaard staat expres op wachten.

Veiligheidsgrenzen die de run stilzetten

Automatisch incasseren betekent dat een fout in de code direct geld van andermans rekening haalt. Daarom checkt de app zichzelf vóór hij ook maar iets boekt: een maximumbedrag per factuur, een maximumtotaal per run, een maximumaantal facturen — en de handigste van de vier, een maximale procentuele afwijking ten opzichte van de vorige maand.

Wat dit afvangt

Precies de categorie fouten die je pas ontdekt als het te laat is: een prijs met een nul te veel, een abonnement dat per ongeluk twee keer geïmporteerd is, of een periodiciteit die op maandelijks staat terwijl hij kwartaal had moeten zijn.

Overschrijdt een run zo’n grens, dan wordt hij niet uitgevoerd. Ik krijg een mail en moet hem handmatig vrijgeven. Geen enkele factuur gaat de deur uit zonder dat er íémand naar gekeken heeft, ook al staat de hele rest van het proces op automatisch.

Nooit twee keer dezelfde factuur

Elk abonnement onthoudt zelf welke periode nog gefactureerd moet worden, en dat veld schuift pas door zodra de factuur écht geboekt is — niet ervoor. Het alternatief, bij elke run uitzoeken wat er al gefactureerd is door de facturentabel te doorzoeken, is fragiel: één verwijderde conceptfactuur of één handmatige correctie en je factureert een maand dubbel of slaat er stiekem een over.

Die garantie zit bovendien niet alleen in de code, maar ook in het datamodel: een unieke index op klant + periode maakt dubbel factureren op databaseniveau letterlijk onmogelijk, zelfs als er ooit per ongeluk twee runs tegelijk zouden starten. Diezelfde gedachte zit achter de factuurnummers — geen “hoogste bestaande nummer + 1”, wat bij twee gelijktijdige runs twee keer hetzelfde nummer geeft, maar een apart tellertje met een rij-lock dat het hele blok nummers vooraf reserveert. Een factuurreeks met een gat of een dubbeling is fiscaal een probleem dat je achteraf niet netjes meer oplost.

Idempotentie zit zelfs op drie niveaus tegelijk: de unieke index in de database vangt twee gelijktijdige runs af, een Idempotency-Key-header op de API vangt een aanroeper op die na een timeout gewoon nog eens probeert, en hetzelfde principe bij Mollie voorkomt een dubbele incasso na een netwerkfout.

Geen wachtwoorden

De app draait op Azure zonder één wachtwoord dat kan lekken. De App Service heeft een eigen managed identity, en die identiteit krijgt rechten op Key Vault, de blobopslag en Document Intelligence — geen sleutel in configuratie, gewoon een rol in Azure. Zelfs de database doet mee: de SQL-server draait op Entra-only authenticatie, dus er bestaat geen SQL-wachtwoord dat geroteerd moet worden. De app logt in met haar identiteit; klaar.

Geheimen worden bovendien niet allemaal hetzelfde behandeld, want dat gaat meestal mis. Er zijn drie categorieën, elk met de tegenovergestelde aanpak:

Soort geheimVoorbeeldBehandeling
Wat de app zelf gebruiktSnelStart- en Mollie-sleutels, databaseverbindingKey Vault, uitgelezen met managed identity
Wat anderen aan ons presenterenAPI-sleutels van koppelingenAlleen een SHA-256-hash in de database — net als bij wachtwoorden
Wat we terug moeten kunnen lezenHMAC-geheim per webhook-abonneeVersleuteld met de ASP.NET Data Protection API

Die middelste categorie is expliciet géén Key Vault: dan zou ik de échte waarde moeten bewaren om te kunnen vergelijken, wat strikt onveiliger is dan hashen, plus een vault-aanroep per binnenkomend verzoek.

Er zijn drie manieren om binnen te komen, en de app kiest zelf welke van toepassing is: staat er een Authorization-header op het verzoek, dan is het een ander programma en geldt de API-sleutel; anders is het een browser en geldt Microsoft Entra ID via Easy Auth. Lokaal log ik automatisch in als beheerder, zonder sleutel of inlogscherm.

Bijna misgegaan

Bij het instellen van Easy Auth moet je expliciet Allow unauthenticated requests kiezen, niet Require authentication. Met “require” onderschept App Service ook /webhooks/mollie en krijgt Mollie een inlogpagina te zien in plaats van mijn app. Elke betaalmelding zou dan verloren gaan, terwijl facturen gewoon op “betaling lopend” blijven staan terwijl het geld allang binnen is. De app bewaakt daarom zelf, per route, welke authenticatie nodig is — in plaats van te vertrouwen op de instelling in het Azure-portaal.

Techniek

De backend is een gelaagde .NET-applicatie: Core (domeinmodel, rekenregels, poorten — geen enkele afhankelijkheid, ook niet op EF Core), Applicatie (facturatieruns, betalingen, planning — kent SQL Server, SnelStart en Mollie alleen via poorten), Infrastructure (EF Core, SnelStart, Mollie, QuestPDF, Azure) en Web (HTTP, beveiliging, achtergrondplanning). De pijlen wijzen één kant op, en dat is geen afspraak maar een gecontroleerd feit: er draaien architectuurtests die falen zodra iemand een verwijzing de verkeerde kant op legt.

Bewuste keuze

Geen react-router. Elke uitgebrachte 7.x-versie draagt op dit moment open beveiligingsadviezen, bijna allemaal over server-side rendering die hier niet gebruikt wordt. Voor financiële software woog dat niet op tegen een afhankelijkheid kunnen verantwoorden — een eigen hash-router van vijftig regels was uiteindelijk minder werk dan die discussie.

Mollie krijgt twee sleutels naast elkaar: een live-sleutel voor echte facturen en een losse test-sleutel voor testfacturen, die met test_ moet beginnen. Daardoor kan ik ook in productie nog een testfactuur maken — die doorloopt de hele keten (PDF, betaallink, mail) maar boekt niets in SnelStart, verbruikt geen echt factuurnummer en gaat altijd naar mijn eigen mailadres. Staat er per ongeluk een live-sleutel in het testveld, dan start de app expres niet.

Naast de maandelijkse run kan ik ook losse facturen maken voor eenmalige klussen, met dezelfde PDF- en boekingslogica. En bonnetjes voor eigen kosten hoef ik niet meer met de hand over te typen: Azure Document Intelligence leest bedrag, datum, leverancier en btw automatisch uit een foto van het bonnetje. Lukt dat een keer niet, dan vul ik het gewoon zelf in — OCR is een hulpmiddel, geen vereiste.

Status

Het systeem draait in Azure en verzorgt de maandelijkse facturatie voor Zwegers IT, van berekening tot boeking tot incasso. De infrastructuur staat volledig als code in Bicep, inclusief een what-if-controle die vóór elke uitrol laat zien wat er zou wijzigen — die controle heeft al eens voorkomen dat een uitrol per ongeluk het grootste deel van de app-configuratie zou wissen.

.NETAzure App ServiceAzure SQL (Entra-only)React 19 + ViteSnelStartMollie
Alle projectenZelf iets slims willen bouwen?
Bel +31 6 44 82 11 28WhatsAppE-mail