Een formulier tegen spam beschermen zonder Captcha: van Akismet naar mijn eigen antispam
In november 2016 publiceerde ik op mijn WordPress-blog een bericht met de titel “Hoe stel je Akismet in?”. Ik legde uit hoe je de antispam-plug-in activeert die met WordPress meekomt, een API-sleutel ophaalt op de officiële site, en vervolgens kiest tussen ongewenste reacties meteen verwijderen of een proefperiode van veertien dagen. Ik deelde zelfs een klein filter om in functions.php te plakken en die termijn naar dertig dagen te brengen, met een zin die eigenlijk alles al samenvatte: “we zijn nooit veilig voor een vals-positief.” Tien jaar later bescherm ik geen blogreacties meer, maar de formulieren van mijn eigen site, gebouwd op Pulsar, mijn PHP-framework. En die reflex uit 2016, liever een twijfelachtig bericht bewaren dan een goed bericht kwijtraken, is de kernregel geworden van de antispam die ik uiteindelijk zelf schreef.
Wat Akismet me had geleerd
Destijds delegeerde ik alles. Akismet combineerde een woordenlijst met ongewenste termen, een set regels en een zwarte lijst, en sorteerde reacties in wachtrij of spam. Voor een blog werkte dat goed en ik raadde het zonder voorbehoud aan.
Maar delegeren heeft een prijs. Elk bericht gaat naar servers van derden voor analyse, wat op een contactformulier waar een prospect zijn naam en e-mailadres achterlaat een echt AVG-vraagstuk is geworden. En vooral: de kost van een vals-positief is van schaal veranderd. Een verloren blogreactie is jammer. Een verloren offerteaanvraag is een klant die niet terugkomt. Mijn oude site bezweek uiteindelijk onder de spam via het contactformulier, en terwijl ik de nieuwe herdacht, legde ik een vertrekvoorwaarde vast: serieus filteren, zonder de rekening ooit bij de mens te leggen.
Waarom geen zichtbare Captcha
Het gebruikelijke antwoord op spam is een Captcha. Ik heb die om drie redenen terzijde geschoven.
De frictie, om te beginnen. Elke stap die je vóór het verzenden toevoegt, doet echte bezoekers afhaken, terwijl serieuze bots die puzzels oplossen of laten oplossen door klikfarms. Je verliest mensen om machines af te remmen die niet meer worden afgeremd.
De toegankelijkheid, vervolgens. Verkeerslichten herkennen in wazige miniaturen sluit slechtziende mensen uit, en de audio-alternatieven zijn voor iedereen vervelend.
De privacy, ten slotte. De Captcha's van de grote platformen laden scripts van derden, observeren het gedrag van de bezoeker en roepen toestemmingsvragen op die ik niet wilde beheren. Mijn site roept geen enkele externe dienst aan voor zijn formulieren: alles is zelf gehost, niets verlaat de deur.
Een Captcha straft de mens voor een probleem dat door bots is veroorzaakt. Ik wilde het omgekeerde: controles die alleen bots opmerken.
Vijf onzichtbare lagen in plaats van een muur
Geen enkele controle is op zichzelf perfect. Gestapeld houden ze het gros van de geautomatiseerde spam tegen zonder dat de bezoeker er iets van merkt. Ik blijf bewust op conceptniveau: veldnamen of exacte drempels publiceren zou neerkomen op het uitdelen van de handleiding om ze te omzeilen.
De honeypot. Een veld dat onzichtbaar is voor mensen en dat bots invullen omdat ze alles invullen. Het detail dat telt: de site antwoordt de bot “bericht verzonden” zonder iets te versturen. Hij weet niet dat hij betrapt is, dus hij leert niets.
De tijdval. Bij het renderen van het formulier geeft de server een cryptografisch ondertekende tijdstempel uit. Een mens doet seconden, vaak minuten, over het lezen en invullen van een formulier. Een bot post vrijwel onmiddellijk. De handtekening maakt het token onvervalsbaar, en de laag blokkeert alleen bij een positief bewijs van automatisering: een ontbrekend of beschadigd token blokkeert nooit, de andere lagen dekken dat geval af. Ze werkt zonder JavaScript.
De onzichtbare challenge. Een kleine cryptografische berekening (proof of work) die de browser helemaal zelf op de achtergrond oplost, in een fractie van een seconde, terwijl de bezoeker typt. Onmerkbaar voor een mens, duur voor een bot die massaal post. Zelf gehost, zonder API-sleutel of dienst van derden. En als de browser geen JavaScript uitvoert, blokkeert het ontbrekende antwoord het verzenden niet: de lagen aan serverkant nemen het over. Graceful degradation geldt ook voor de beveiliging.
De snelheidsbegrenzing. Een bot die het formulier vanaf hetzelfde adres overspoelt, wordt na enkele pogingen afgeremd. Het IP-adres wordt gehasht vóór elke opslag, ik bewaar het nooit leesbaar.
De inhoudsscore. Linkdichtheid, tekstkwaliteit, detectie van duplicaten. Elk signaal telt punten bij een score op, en hier wijkt de filosofie af van klassieke filters: die score wijst nooit af.
De regel van nul verloren leads
Mijn pipeline onderscheidt twee families van controles. Positieve bewijzen van automatisering, zoals de honeypot of de tijdval, wijzen af. Inhoudssignalen wijzen nooit af: een bericht met een hoge score wordt toch bezorgd, gewoon gemarkeerd voor handmatige controle in mijn inbox.
Een echte klant die drie links naar zijn bestaande site plakt, of twee droge regels vanaf zijn telefoon schrijft, komt erdoor. Ik lees liever twee spamberichten dan dat ik een aanvraag verlies. Dat is precies de logica van mijn dertig dagen Akismet-proefperiode in 2016, tot het einde doorgetrokken: twijfel valt altijd in het voordeel van het bericht uit.
Wat het in de praktijk oplevert
Op de contactpagina en op de offertecalculator ziet de bezoeker hier niets van. Geen vakje “ik ben geen robot”, geen puzzel, geen toestemmingsbanner voor een script van derden. Het formulier vult zich in en verzendt, klaar.
Als jouw eigen formulier bezwijkt onder spam, past mijn suggestie in één zin: voordat je een Captcha toevoegt die je bezoekers wegjaagt, stapel onzichtbare controles en houd de afwijzing voor bewijzen van automatisering. In 2016 sloot ik mijn bericht af met een uitnodiging om te reageren en te delen. In 2026 is de eerlijke versie eenvoudiger: als het onderwerp je aanspreekt, schrijf me dan via dat befaamde formulier. Het houdt stand.