Webhook: wat is het en hoe koppel je AI aan formulieren?
Leer wat een webhook is en koppel AI aan formulieren: FastAPI ontvangt data, AI classificeert het bericht en slaat het op in CSV. Ook met Make en Zapier.
👁 114 keer gelezen
Een webhook is een URL waar een applicatie automatisch gegevens naartoe stuurt zodra er iets gebeurt: iemand vult een formulier in, er komt een e-mail binnen of er is een betaling verricht. In plaats van elke minuut te vragen “is er al iets nieuws?”, wacht je rustig af tot er wordt aangeklopt. Hieronder laat ik een webhook in de praktijk zien: een lokale Python-server (FastAPI) ontvangt een formulierinzending, een AI-model classificeert het bericht en stelt een conceptantwoord op, waarna het resultaat in een spreadsheet terechtkomt. Daarna leg ik dezelfde flow uit in Make en Zapier. Dit artikel is onderdeel van het leertraject AI-agents vanaf nul, binnen de automatiseringsfase.
Status op 4 oktober 2026
- Test: Windows 11, Python 3.13, FastAPI 0.142.2, uvicorn 0.54.0, model
openai/gpt-5.6-lunavia OpenRouter. - Enkele verwerking: 1,7-2,1 s van verzending tot respons, kosten ongeveer ca. € 0,00014 (0,00016 USD; omgerekend volgens de ECB-referentiekoers van 12-10-2026 via ecb.europa.eu).
- Make: gratis abonnement biedt 1000 credits per maand en 2 actieve scenario’s. Zapier: 100 taken per maand, enkel tweestaps-Zaps, waarbij webhooks beschikbaar zijn in betaalde abonnementen (vanaf ca. € 17,88 (19,99 USD) per maand bij jaarlijkse betaling; wisselkoers ECB van 12-10-2026 via ecb.europa.eu).
- Code: starterpakket starterarchief-voor-agenten, bestanden
06_webhook.pyen06_test_webhook.py.
Webhook in eenvoudige woorden
Er zijn twee manieren waarop een programma op de hoogte raakt van nieuwe gebeurtenissen:
- periodiek pollen (polling): je programma kijkt bijvoorbeeld elke 5 minuten in een inbox of sheet of er iets nieuws is bijgekomen. Eenvoudig, maar vertraagd en verspilt onnodige API-verzoeken;
- webhook: je geeft een externe applicatie jouw URL (bijv.
https://jouwdomein.nl/webhook/formulier), en die app stuurt direct een HTTP POST-verzoek met JSON-data zodra er een gebeurtenis plaatsvindt.
Een webhook is in feite een gewoon API-verzoek, maar dan omgekeerd: jij roept de dienst niet aan, de dienst roept jou aan. Als HTTP-verzoeken en JSON nieuw voor je zijn, begin dan met de gids over API’s.
Typische bronnen van webhooks:
- formulieren (Tally, Typeform, Google Formulieren via script, WordPress-formulieren);
- betalingssystemen (Stripe, Mollie sturen een seintje bij betaling);
- webshops (WooCommerce, Shopify: nieuwe bestellingen);
- productiviteitstools (GitHub, Slack, Trello, Calendly);
- automatiseringsplatforms (Make, Zapier en n8n hebben kant-en-klare modules om webhooks te ontvangen).
Wat we gaan bouwen
- Een contactformulier verstuurt een JSON-pakket: naam, e-mail en bericht.
- Mijn server controleert een geheim token in de header (zodat onbevoegden geen rommel kunnen insturen).
- Het AI-model verwerkt het bericht en geeft een JSON terug: categorie, urgentie van 1 tot 3, samenvatting en een conceptantwoord.
- De server voegt een rij toe aan een CSV-bestand (een lokaal equivalent van een Google Sheet) en stuurt het resultaat terug.
Code van de webhook-server
Installatie: pip install fastapi uvicorn openai python-dotenv. In het bestand .env plaats je de sleutel OPENROUTER_API_KEY en WEBHOOK_SECRET (een lange, willekeurige tekenreeks).
"""06 - Webhook: formulier stuurt inzending -> server vraagt model -> resultaat komt in CSV en als respons.
Starten: uvicorn 06_webhook:app --port 8000 (of: python 06_webhook.py)
Testen: curl -X POST http://127.0.0.1:8000/webhook/formulier -H "X-Webhook-Secret: ..." -H "Content-Type: application/json"
-d "{\"naam\":\"Ola\",\"email\":\"ola@example.com\",\"bericht\":\"Factuur heeft een onjuist btw-nummer\"}"
"""
import csv, datetime, json, os, pathlib
from dotenv import load_dotenv
from fastapi import FastAPI, Header, HTTPException
from openai import OpenAI
from pydantic import BaseModel
load_dotenv()
SEKRET = os.getenv("WEBHOOK_SECRET", "verander-mij") # dezelfde string vul je in bij Make/Zapier/formulier
MODEL = os.getenv("MODEL", "openai/gpt-5.6-luna")
ARKUSZ = pathlib.Path("inzendingen.csv")
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])
app = FastAPI()
class Inzending(BaseModel): # structuur van de JSON die het formulier verstuurt
naam: str
email: str
bericht: str
@app.post("/webhook/formulier")
def ontvang(z: Inzending, x_webhook_secret: str = Header(default="")):
if x_webhook_secret != SEKRET: # zonder dit kan iedereen met de URL data sturen en tokens verbruiken
raise HTTPException(status_code=401, detail="ongeldig geheim")
# Vertaling van de geteste Poolse systeemprompt:
r = client.chat.completions.create(model=MODEL, response_format={"type": "json_object"}, messages=[
{"role": "system", "content": "Je bent een helpdeskassistent. Geef JSON terug met de velden: categorie (facturen|klacht|"
"offerte|overig), urgentie (1-3, 3 = urgent), samenvatting (max 12 woorden), concept_antwoord (2-3 zinnen, informeel)."},
{"role": "user", "content": f"Van: {z.naam}\nBericht: {z.bericht}"}])
wynik = json.loads(r.choices[0].message.content)
nieuw = not ARKUSZ.exists()
with ARKUSZ.open("a", newline="", encoding="utf-8-sig") as f: # utf-8-sig: Excel opent speciale tekens correct
w = csv.writer(f, delimiter=";")
if nieuw: w.writerow(["tijdstip", "naam", "email", "categorie", "urgentie", "samenvatting", "concept_antwoord"])
w.writerow([datetime.datetime.now().isoformat(timespec="seconds"), z.naam, z.email, wynik.get("categorie"),
wynik.get("urgentie"), wynik.get("samenvatting"), wynik.get("concept_antwoord")])
return {"ok": True, **wynik, "kosten_usd": getattr(r.usage, "cost", None)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Belangrijke details in deze opzet:
class Inzending(BaseModel)definieert het verwachte JSON-formaat. FastAPI weigert verzoeken zonder vereiste velden direct (foutmelding 422), nog voordat er tokens worden aangesproken;- het geheime token in de header
X-Webhook-Secret. Een geraden URL zonder secret krijgt status 401 en kost niets; response_format={"type": "json_object"}dwingt het model een zuivere JSON te leveren, die direct in tabellen kan worden opgeslagen;utf-8-sigbij het opslaan van de CSV zorgt ervoor dat Excel leestekens direct foutloos toont.
Uitvoering en test
In het ene terminalvenster start ik de server: uvicorn 06_webhook:app --port 8000. In een tweede venster start ik een script dat een formulier simuleert en drie inzendingen verstuurt, plus eentje met een ongeldig geheim. We hebben dit oorspronkelijk in het Pools getest:
"""Verstuurt 3 voorbeeldinzendingen naar de lokale webhook (zoals een formulier of Make/Zapier dat zou doen)."""
import os, time, requests
URL = "http://127.0.0.1:8000/webhook/formulier"
SEKRET = os.getenv("WEBHOOK_SECRET", "verander-mij")
print("ongeldig geheim ->", requests.post(URL, json={"naam": "X", "email": "x@example.com", "bericht": "test"},
headers={"X-Webhook-Secret": "geraden"}).status_code)
# Voorbeeldberichten (vertaling van de oorspronkelijk geteste Poolse teksten):
for naam, inhoud in [("Ola", "Goedendag, op factuur FV/2026/09/027 staat een verkeerd btw-nummer van mijn bedrijf. Ik heb voor vrijdag een correctie nodig, want dan sluit ik de maand af."),
("Marek", "Wat kost het implementeren van een chatbot op een webshop met 300 producten? Ik mik op een oplevering in november."),
("Kasia", "De bestelde kabel is beschadigd binnengekomen, de stekker is gebroken. Ik wil mijn geld terug.")]:
t = time.time()
r = requests.post(URL, json={"naam": naam, "email": f"{naam.lower()}@example.com", "bericht": inhoud},
headers={"X-Webhook-Secret": SEKRET}, timeout=60)
print(f"\n{naam}: {inhoud}\n-> HTTP {r.status_code} in {time.time() - t:.1f} s: {r.json()}")
Het resultaat van 4 oktober, 14:11 (let op: de test werd in het Pools uitgevoerd, onderstaande output toont de oorspronkelijke Poolse antwoorden van het model):
> python 06_test_webhook.py
ongeldig geheim -> 401
Ola (vertaling van het Poolse origineel): Goedemiddag, op factuur FV/2026/09/027 staat een verkeerd btw-nummer van mijn bedrijf. Ik heb voor vrijdag een correctie nodig, want ik sluit de maand af.
-> HTTP 200 in 2.1 s: {'ok': True, 'categorie': 'facturen', 'urgentie': 3, 'samenvatting': 'Onjuist btw-nummer op de factuur, correctie nodig voor vrijdag', 'concept_antwoord': 'Bedankt voor het bericht - we controleren factuur FV/2026/09/027 en bereiden een correctie voor met het juiste btw-nummer. We pakken dit met spoed op, zodat het document voor vrijdag klaar is.', 'kosten_usd': 0.0001746}
Marek (vertaling van het Poolse origineel): Hoeveel kost het implementeren van een chatbot op een winkelwebsite met 300 producten? Ik ben geïnteresseerd in een termijn voor november.
-> HTTP 200 in 1.8 s: {'ok': True, 'categorie': 'offerte', 'urgentie': 2, 'samenvatting': 'Vraag over de kosten en planning voor het implementeren van een chatbot voor een winkel', 'concept_antwoord': 'Hallo Marek, we maken een offerte voor de implementatie van een chatbot voor een winkel met 300 producten en controleren de beschikbaarheid in november. Stuur ons alsjeblieft het winkeladres en laat weten welke functies de chatbot moet ondersteunen.', 'kosten_usd': 0.000162}
Kasia (vertaling van het Poolse origineel): De bestelde kabel is beschadigd aangekomen, de stekker is gebarsten. Ik wil mijn geld terug.
-> HTTP 200 in 1.7 s: {'ok': True, 'categorie': 'klacht', 'urgentie': 2, 'samenvatting': 'Beschadigde kabel, gebarsten stekker; klant wil geld terug.', 'concept_antwoord': 'Het spijt ons dat de kabel beschadigd is aangekomen. Stuur ons alsjeblieft foto\'s van de schade en het bestelnummer, dan regelen we de terugbetaling.', 'kosten_usd': 0.0001504}
> type inzendingen.csv
tijd;naam;email;categorie;urgentie;samenvatting;concept_antwoord
2026-10-04T14:11:16;Ola;ola@example.com;facturen;3;Onjuist btw-nummer op de factuur, correctie nodig voor vrijdag;Bedankt voor het bericht - we controleren factuur FV/2026/09/027 en bereiden een correctie voor met het juiste btw-nummer. We pakken dit met spoed op, zodat het document voor vrijdag klaar is.
2026-10-04T14:11:18;Marek;marek@example.com;offerte;2;Vraag over de kosten en planning voor het implementeren van een chatbot voor een winkel;Hallo Marek, we maken een offerte voor de implementatie van een chatbot voor een winkel met 300 producten en controleren de beschikbaarheid in november. Stuur ons alsjeblieft het winkeladres en laat weten welke functies de chatbot moet ondersteunen.
2026-10-04T14:11:19;Kasia;kasia@example.com;klacht;2;"Beschadigde kabel, gebarsten stekker; klant wil geld terug.";Het spijt ons dat de kabel beschadigd is aangekomen. Stuur ons alsjeblieft foto\'s van de schade en het bestelnummer, dan regelen we de terugbetaling.
Wat hier opvalt:
- het verzoek met het foute geheim werd afgewezen met HTTP 401, zonder het model aan te spreken;
- de categorieën klopten in alle drie de gevallen. Urgentie 3 werd enkel toegekend aan het bericht met een deadline (“voor vrijdag”), de rest kreeg urgentie 2;
- de conceptantwoorden waren relevant, en bij de klacht vroeg het model uit zichzelf om foto’s en een bestelnummer;
- het model gebruikte eenmaal een lang liggend streepje in plaats van een gewoon koppelteken. Als je strikte stijlgidsen hanteert, neem die dan op in de systeemprompt.
Een valkuil tijdens het testen: curl in Git Bash op Windows met niet-standaard tekens direct in -d '...' veroorzaakte coderingsproblemen, waardoor de server reageerde met “There was an error parsing the body” (status 400). Dit werd opgelost door de JSON op te slaan in een bestand en mee te sturen via --data-binary @bestand.json, of simpelweg via het bovenstaande Python-script.
Hoe breng je een webhook online?
Een server op 127.0.0.1 is alleen bereikbaar op je eigen computer. Om een formulier vanaf internet signalen te laten sturen, heb je een publiek adres nodig:
- voor tests: een tunnel zoals ngrok of Cloudflare Tunnel, die een tijdelijke HTTPS-URL doorstuurt naar jouw lokale poort 8000;
- permanent: een kleine VPS of cloudplatform, zoals Railway, waar je hetzelfde script laat draaien en een permanent adres krijgt;
- zonder servercode: vang de webhook op in Make, Zapier of n8n (zie hieronder) en roep vanuit daar het model aan.
Gebruik altijd HTTPS en een geheim token. Grote platformen (zoals Stripe en GitHub) ondertekenen elk verzoek bovendien met een HMAC-sleutel. In dat geval vergelijk je geen statisch wachtwoord, maar bereken je de handtekening van de payload en toets je die aan de meegestuurde header.
Dezelfde automatisering in Make (no-code)
- Maak een scenario aan en kies als eerste module Webhooks, Custom webhook. Make genereert direct een unieke URL.
- Plak deze URL in de instellingen van je formulierentool (zoals Tally, Typeform of WordPress) en stuur een testbericht. Make detecteert direct de JSON-structuur.
- Voeg een AI-module toe: OpenAI (of een HTTP-module gekoppeld aan OpenRouter) met een prompt die om een JSON-structuur vraagt.
- Voeg de module JSON, Parse JSON toe om het modelantwoord op te splitsen in losse variabelen.
- Koppel Google Sheets, Add a row en wijs de velden toe aan de juiste kolommen.
- Optioneel: voeg een filter toe op “urgentie = 3” gevolgd door een notificatiemodule (zoals Slack of e-mail).
Elke doorgifte kost in Make enkele credits (één per module per run). Het gratis abonnement biedt 1000 credits per maand en 2 actieve scenario’s. Lees meer details in ons artikel over hoe Make werkt en wat het kost.
Dezelfde flow in Zapier
- Trigger: Webhooks by Zapier, Catch Hook (de URL die je in het formulier plakt). Of gebruik een directe formuliertrigger als Zapier die ondersteunt.
- Actie: ChatGPT of AI by Zapier met een classificatieopdracht.
- Actie: Google Sheets, Create Spreadsheet Row.
Webhooks by Zapier is een zogeheten premium-app. Je hebt hiervoor een betaald abonnement nodig (Professional vanaf ca. € 17,88 (19,99 USD) per maand bij jaarbetaling, of ca. € 26,82 (29,99 USD) per maand bij maandelijkse facturatie; wisselkoers ECB van 12-10-2026 via ecb.europa.eu). Daarnaast telt deze Zap drie stappen, wat de limiet van het gratis plan overschrijdt. Een uitgebreide vergelijking vind je in het artikel over Zapier: functies en kosten.
Zelf programmeren, Make of Zapier: wat kies je?
| Methode | Vaste kosten | Vereisten | Geschikt voor |
|---|---|---|---|
| Eigen server (FastAPI) | Hosting (gratis lokaal met tunnel) | Basis Python-kennis | Wie volledige controle wil en niet per uitvoeringsstap wil betalen |
| n8n (self-hosted) | Gratis Community-editie, hostingkosten voor VPS | Eigen serverbeheer (bijv. VPS) | Veel automatiseringen met data in eigen beheer |
| Make | Vanaf € 0 (1000 credits) | Alleen een browser | Middelgrote scenario’s, vertakkende logica |
| Zapier | Webhooks vereisen Professional-plan | Alleen een browser | Wie de meeste kant-en-klare koppelingen en de snelste start zoekt |
Veelgestelde vragen
Wat is een webhook in eenvoudige woorden?
Een webhook is een webadres waar een applicatie automatisch informatie naartoe stuurt zodra er iets relevants gebeurt, inclusief de bijbehorende data van die gebeurtenis.
Wat is het verschil tussen een webhook en een API?
Een gewone API is een deur waar jij op klopt zodra je iets nodig hebt. Een webhook is een deurbel die bij jou afgaat zodra de andere partij iets te melden heeft. Technisch gezien is een webhook gewoon een inkomend verzoek aan jouw eigen API.
Is een webhook veilig?
Een webhook is zo veilig als je hem zelf inricht. De basisvereiste is HTTPS met een geheim token in de header. Voor robuuste beveiliging voeg je een HMAC-handtekening toe, controleer je timestamps (tegen replay-aanvallen) en limiteer je de omvang van inkomende verzoeken.
Hoe test je een webhook zonder publieke server?
Je kunt verzoeken lokaal nabootsen via een eigen testscript gericht op 127.0.0.1 (zoals in dit artikel getoond) of een tunneldienst zoals ngrok gebruiken die openbaar HTTPS-verkeer doorleidt naar je ontwikkelcomputer.
Wat gebeurt er als mijn server offline is?
Dat hangt af van de afzender. Diensten als Stripe of GitHub proberen verzoeken gedurende langere tijd opnieuw te verzenden. Een eenvoudig webformulier raakt de inzending mogelijk kwijt. Daarom moet een webhook-endpoint altijd snel antwoorden en zwaardere taken, zoals modelverwerking bij veel verkeer, asynchroon op de achtergrond afhandelen.
Kan ik een webhook koppelen aan een AI-agent?
Zeker, dat is zelfs de standaardmethode om agents te triggeren: de gebeurtenis komt binnen via een webhook en in plaats van één statische modelaanroep start er een agent-loop die zelfstandig acties en tools inzet. Hoe je dat programmeert, lees je in hoe je een AI-agent bouwt in Python.
Google · Jouw bronnenHoger in Google - met één klikToevoegen aan voorkeursbronnen →Bronnen en controledatum
- Prijzen en abonnementen: Make, Zapier, n8n (gecontroleerd op 4 oktober 2026).
- FastAPI Documentatie.
- Test: uitgevoerd op eigen computer op 4 oktober 2026 om 14:11. De geteste contactberichten bevatten fictieve gegevens.


