Een contactformulier op een static website, zonder afhankelijk te zijn van derden

684 woorden, 3 minuten leestijd
Door: Sjoerd Blom
Sjoerd Blom Sjoerd Blom
Sjoerd Blom is getrouwd en trotse vader van 2 meiden. Hij is dol op lekker eten, reizen en technische snufjes. Sjoerd schrijft vooral over de wereld, reizen en technologie.

Static websites hebben veel voordelen. Ze zijn snel, veilig, eenvoudig te hosten en prettig te onderhouden. Met een generator als Hugo staat er in no-time een razendsnelle site zonder database of complexe backend.

Maar er is één klassiek pijnpunt: het contactformulier.

Zonder server-side logica ben je al snel aangewezen op externe diensten zoals Formspree, Getform of de forms-functionaliteit van Netlify. Dat werkt. Vaak zelfs gratis of voor weinig geld.

Toch wringt daar iets fundamenteels.

Je levert de regie over je data uit.

Een externe partij bepaalt:

  • of een formulier überhaupt wordt geaccepteerd
  • wanneer iets als spam wordt gezien
  • hoe en waar jouw data wordt opgeslagen
  • welke metadata wordt gelogd

Ik vind het een vreemde gedachte dat een derde partij bepaalt of iemand mij mag contacteren.

Dus heb ik mijn contactformulier opnieuw actief gemaakt. Volledig onder eigen beheer. De kern: n8n.


n8n is in de basis een workflow-automation tool. Denk aan een open-source alternatief voor Zapier. Maar omdat je het self-hosted kunt draaien, leent het zich uitstekend als lichtgewicht backend voor een static site.

Wat mij aanspreekt:

  • self-hosted
  • volledig transparant
  • geen verplichte database
  • flexibel uitbreidbaar
  • volledige controle over logging, validatie en beveiliging

Mijn contactformulier post rechtstreeks naar een n8n webhook. Alles wat daarna gebeurt, bepaal ik zelf. Geen black box. Geen verborgen “anti-spam scoring”. Geen beleid dat plots verandert.


De workflow heb ik bewust stateless opgezet. Geen database, geen externe opslag, geen sessies. Alles wordt gevalideerd op het moment van binnenkomst.

De workflow-JSON stel ik binnenkort beschikbaar, ik wil namelijk nog wel mijn workflow praktisch getest hebben. Maar de globale afhandeling ziet er zo uit:

  1. Webhook ontvangt de POST-request
  2. Velden worden genormaliseerd
  3. Verkeer wordt gefilterd en gevalideerd
  4. Alleen geldige berichten worden doorgelaten
  5. Een e-mail wordt verstuurd
  6. De client krijgt een nette JSON-response terug

Eenvoudig in opzet. Strikt in uitvoering.


Dit formulier is nadrukkelijk niet “simpel”. Een publiek endpoint moet defensief ontworpen zijn. Zeker zonder traditionele backend.

Alleen verkeer van bekende IP-adressen wordt geaccepteerd. Alles daarbuiten krijgt direct een 403-response.

Geschikt voor:

  • eigen websites
  • eigen servers
  • testomgevingen

Geen bekende herkomst? Geen toegang.

Het formulier bevat een verborgen veld dat normale gebruikers nooit invullen. Bots vaak wel.

Het resultaat:

  • geen foutmelding
  • wel een “ok” response
  • geen verdere verwerking

Bots krijgen geen signaal dat ze zijn afgevangen. Dat maakt het minder aantrekkelijk om te blijven proberen.

Naam, e-mail en bericht worden inhoudelijk gecontroleerd op:

  • minimale lengte
  • geldig e-mailformaat

Onjuiste input krijgt een duidelijke 400-response. Geen vage “er ging iets mis”.

Elke formulier-submit bevat:

  • een nonce
  • een expiry timestamp
  • een HMAC-signature

De nonce:

  • is tijdsgebonden
  • is cryptografisch beschermd
  • vereist een server-side secret

Dit voorkomt replay-aanvallen en scripted spam.

Alle cryptografische vergelijkingen worden uitgevoerd met timing-safe methodes. Geen subtiele lekken via timing-attacks.

Klein detail, groot verschil.

Naast de nonce kan de volledige payload worden gesigneerd. Dat is vooral handig wanneer je het formulier vanaf meerdere frontends gebruikt.

Zo weet je zeker dat de inhoud onderweg niet ongemerkt is aangepast.

Per IP-adres geldt een maximum aantal requests per uur.

Geïmplementeerd via n8n workflow static data. Dus:

  • geen aparte database
  • geen Redis
  • geen extra infrastructuur

Wel effectief.

Elke foutconditie heeft een eigen HTTP-statuscode:

  • 401 voor authenticatieproblemen
  • 403 voor IP-blokkades
  • 429 voor rate limiting

Geen generieke foutpagina’s, maar voorspelbaar gedrag.


Samengevat:

  • mijn data blijft van mij
  • geen afhankelijkheid van beleid van derden
  • geen verborgen logging
  • geen lock-in
  • volledig inzicht in de hele keten

Ja, dit kost meer denkwerk dan een embed-scriptje plakken.

Maar het past bij hoe ik mijn infrastructuur wil inrichten: bewust, minimalistisch en onder eigen controle. Net zoals ik eerder koos voor een static site met Hugo in plaats van een traditionele CMS-setup.


Een static website hoeft geen concessies te doen op functionaliteit of veiligheid.

Met een tool als n8n bouw je precies datgene wat je nodig hebt. Niet meer. Niet minder.

Mijn contactformulier is weer live. Onder mijn voorwaarden.

Wie dit interessant vindt, mag me nu ook daadwerkelijk bereiken. Via dat formulier.