Webhooks instellen en beveiligen
Met een webhook stuurt LeadBot elke nieuwe lead automatisch door naar je CRM, datawarehouse of eigen applicatie. In dit artikel maak je een webhook aan, controleer je de handtekening en los je mislukte bezorgingen op.
Wat is een webhook?
Een webhook is een automatisch HTTP POST-verzoek dat LeadBot naar een URL van jouw systeem stuurt zodra er iets gebeurt, bijvoorbeeld wanneer een bezoeker een formulier verstuurt. Het verzoek bevat de lead als JSON: de contactgegevens, de LeadBot, het ingevulde formulier, de pagina waarop de bezoeker de actie uitvoerde en de campagne waar hij vandaan komt.
Nieuwe leads zie je normaal in je Inbox en in je e-mailnotificaties. Wil je ze ook automatisch in een ander systeem verwerken, dan regel je dat met een webhook: LeadBot pusht de gegevens direct naar jouw systeem, zonder handwerk en zonder vertraging. Typische toepassingen:
Voor een webhook heb je een publiek bereikbare URL nodig (bij voorkeur HTTPS) die JSON-POST-verzoeken kan ontvangen. Het bouwen van zo'n endpoint is werk voor een developer; de rest van dit artikel is daarom technischer dan onze andere artikelen.
- CRM — maak van elke inzending automatisch een lead of contact aan.
- Automatisering — start een eigen workflow na een formulier, WhatsApp-bericht, terugbelverzoek of e-mailverzoek.
- Rapportage — stuur conversies door naar een datawarehouse of dashboard.
Webhook aanmaken
Webhooks beheer je in het LeadBot-dashboard. Eén webhook kan naar meerdere events luisteren; maak voor verschillende ontvangende systemen aparte webhooks aan, dan houd je de instellingen en bezorglogs gescheiden.
Bij het aanmaken vul je alleen een naam, de URL, een omschrijving en de events in — er is geen veld voor een secret. LeadBot genereert die dus altijd zelf. Je vindt de secret terug op de detailpagina van de webhook, onder Geavanceerde instellingen, en daar kun je hem ook aanpassen. Een eigen secret meteen meegeven kan alleen via de API. Behandel hem als een wachtwoord: zet hem op je server in een omgevingsvariabele of secrets manager, nooit in je broncode.
- 1Ga in de zijbalk naar Webhooks en klik op Webhook toevoegen.
- 2Geef de webhook een herkenbare naam en vul de URL van je endpoint in.
- 3Selecteer de events die je wilt ontvangen.
- 4Sla de webhook op.
- 5Open de webhook en klik op Webhook testen om een voorbeeldlead te versturen. De test gebruikt de velden van je eigen formulier, met voorbeeldantwoorden, zodat een veldkoppeling die je op de test bouwt ook voor echte leads werkt.
- 6Controleer je eigen serverlog en het tabblad Bezorginformatie van de webhook.
Beschikbare events
Selecteer alleen de events die je echt nodig hebt. In elk verzoek zie je aan het veld event in de JSON en aan de header X-Webhook-Event welk event het verzoek veroorzaakte.
- visitor.form_submission — een bezoeker verstuurt een formulier.
- visitor.whatsapp_message — een bezoeker verstuurt een WhatsApp-bericht via LeadBot.
- visitor.callback_request — een bezoeker vraagt om teruggebeld te worden.
- visitor.email_request — een bezoeker vraagt om contact per e-mail.
- visitor.ai_chat — een AI-chatgesprek is afgerond. In het dashboard heet dit event 'AI-chat afgerond'. Het wordt eenmaal per gesprek verstuurd, nadat de chat vijf minuten stil is geweest; elk nieuw bericht start die wachttijd opnieuw. Dit event heeft een andere payload dan de vier events hierboven — zie 'Payload van visitor.ai_chat' verderop.
Voorbeeld van de payload (interactie-events)
De vier interactie-events hieronder — form_submission, whatsapp_message, callback_request en email_request — hebben allemaal dezelfde payload. Elke payload heeft een uniek event-id, het eventtype en het tijdstip. Contactvelden kunnen null zijn als de bezoeker ze niet heeft ingevuld. Alle overige velden — page_url, referrer, form_title, leadbot_id, leadbot_name, form_data en de negen campagnevelden — zitten er altijd in, met null als waarde wanneer LeadBot ze niet kent. De sleutels verdwijnen dus nooit, zodat de vorm van de payload bij elke bezorging gelijk is — daar rekenen onder andere Zapier-triggers op.
Het veld created is een ISO 8601-tijdstempel met microseconden en een expliciete UTC-offset, bijvoorbeeld 2026-07-16T09:42:13.123456+00:00. Er zit dus geen 'Z' op het eind; gebruik een volwaardige ISO 8601-parser.
In form_data gebruikt LeadBot de zichtbare veldlabels uit je flow als keys. Komt hetzelfde label vaker voor, dan krijgt een volgende key een nummer, bijvoorbeeld Interesse_2. Houd er in je integratie rekening mee dat deze keys per formulier verschillen en mee veranderen wanneer iemand de labels in de flow aanpast.
Koppel contactgegevens daarom aan de vaste velden email, phone en name bovenaan de payload, niet aan de keys in form_data. De vaste velden zijn bij elk formulier en elk event hetzelfde. Ze komen uit het profiel van de bezoeker: email en phone zijn de laatst ingevulde waarde, name de eerst ingevulde naam. Heeft je formulier aparte velden zoals Voornaam, Tussenvoegsel en Achternaam, dan voegt LeadBot die samen tot name. Gebruik form_data voor de overige antwoorden.
Het veld referrer is de pagina waar de bezoeker vandaan kwam voordat hij op page_url belandde, bijvoorbeeld een zoekmachine of een verwijzende site. Bij direct verkeer is het null. Gevoelige queryparameters — tokens, sleutels, e-mailadressen — worden eruit gefilterd voordat LeadBot de URL opslaat.
Het veld form_title is de titel van het formulier dat de bezoeker verstuurde, zoals die in je flow staat. Heeft één LeadBot meerdere formulieren, dan zie je hieraan welk formulier is ingevuld. LeadBot legt de titel bij de inzending vast, dus oude leads houden de titel die het formulier op dat moment had, ook als je de stap later hernoemt of verwijdert.
{
"id": "f56fb9f5-34b5-4a98-a201-fcfd2061b023",
"event": "visitor.form_submission",
"created": "2026-07-16T09:42:13.123456+00:00",
"email": "bezoeker@example.com",
"phone": "+31612345678",
"name": "Robin de Vries",
"hostname": "example.com",
"page_url": "https://example.com/contact",
"referrer": "https://www.google.com/",
"form_title": "Offerte aanvragen",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "voorjaarsactie",
"utm_term": null,
"utm_content": null,
"gclid": "EAIaIQobChMIsample",
"wbraid": null,
"gbraid": null,
"gad_campaignid": null,
"leadbot_id": 123,
"leadbot_name": "Contact",
"form_data": {
"E-mailadres": "bezoeker@example.com",
"Vraag": "Ik ontvang graag meer informatie"
}
}Campagnegegevens in de payload
Elke payload bevat negen campagnevelden. Daarmee weet je in je CRM meteen uit welke campagne een lead komt, zonder zelf de URL te hoeven ontleden.
De waarden komen van de sessie van de bezoeker, niet van de pagina waarop hij de actie uitvoerde. LeadBot legt ze vast op de eerste pagina van het bezoek (first touch) en houdt ze de hele sessie vast: landt iemand op /?utm_source=google&utm_medium=cpc en verstuurt hij drie pagina's verder een formulier, dan staat er in de payload nog steeds utm_source: "google". Een sessie loopt door tot de bezoeker 30 minuten inactief is; daarna begint een nieuwe sessie die de attributie opnieuw vastlegt. Komt dezelfde bezoeker een dag later rechtstreeks terug en vult hij dan een formulier in, dan zijn de campagnevelden null.
Herkent LeadBot een Google Ads-klik (gclid, wbraid, gbraid of gad_campaignid) terwijl er geen utm_source in de URL staat, dan vult LeadBot utm_source aan met 'google' en utm_medium met 'cpc'. Zo staat betaald zoekverkeer ook zonder UTM-tags netjes in je rapportage.
Alle negen sleutels zitten altijd in de payload, ook bij direct verkeer — dan met null als waarde. Ook de voorbeeldpayload van Webhook testen bevat ze, zodat je je koppeling en je veldtoewijzingen kunt maken voordat de eerste echte campagnelead binnenkomt.
- utm_source, utm_medium, utm_campaign — de standaard UTM-tags uit je advertentie-, e-mail- of socialelinks: respectievelijk de bron, het kanaal en de campagnenaam.
- utm_term, utm_content — de optionele UTM-tags voor zoekwoord en advertentievariant; meestal null, tenzij je ze zelf in je links zet.
- gclid, wbraid, gbraid — de klik-ID's die Google Ads aan je URL toevoegt. Ze zijn versleuteld en alleen bruikbaar via de Google Ads-API, bijvoorbeeld om offline conversies terug te melden.
- gad_campaignid — het campagne-ID dat Google Ads aan de final URL toevoegt. Anders dan de klik-ID's is dit een leesbaar nummer dat je direct aan een campagne in je Google Ads-account koppelt.
Payload van visitor.ai_chat
Het event visitor.ai_chat heeft een eigen payload. De event-, contact-, context- en campagnevelden zijn hetzelfde, maar in plaats van form_data krijg je de gegevens van het gesprek: conversation_id, started_at, last_message_at, message_count, transcript (het hele gesprek als platte tekst) en messages (dezelfde berichten als lijst, met role 'visitor' of 'assistant'). De velden form_title en form_data ontbreken hier: een chatstap heeft geen formulier. Verwerk je dit event, bouw daar dan een apart pad voor: code die form_data verwacht, loopt hierop vast.
LeadBot verstuurt het event nadat het gesprek vijf minuten stil is geweest, met de transcriptie tot dat moment. Gaat de bezoeker daarna verder in hetzelfde gesprek, dan volgt er later een tweede bezorging met dezelfde conversation_id en de aangevulde transcriptie.
{
"id": "0f4a2f1c-2f7e-4a3c-9a0b-6a71b8e7c2d1",
"event": "visitor.ai_chat",
"created": "2026-07-16T09:47:31.884210+00:00",
"email": "bezoeker@example.com",
"phone": null,
"name": null,
"hostname": "example.com",
"page_url": "https://example.com/prijzen",
"referrer": "https://www.google.com/",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "voorjaarsactie",
"utm_term": null,
"utm_content": null,
"gclid": "EAIaIQobChMIsample",
"wbraid": null,
"gbraid": null,
"gad_campaignid": null,
"conversation_id": "3b0f9d2a-5f4c-4a1e-8f61-0c2a3f9a77bd",
"started_at": "2026-07-16T09:41:02.117043+00:00",
"last_message_at": "2026-07-16T09:42:31.884210+00:00",
"message_count": 4,
"transcript": "Visitor: Wat kost het?\n\nAssistant: Dat hangt af van je pakket.",
"messages": [
{ "role": "visitor", "message": "Wat kost het?" },
{ "role": "assistant", "message": "Dat hangt af van je pakket." }
],
"leadbot_id": 123,
"leadbot_name": "Contact"
}Headers en dubbele bezorgingen
LeadBot stuurt bij elk verzoek Content-Type: application/json en User-Agent: Leadbot-Webhooks/1.0 mee, plus drie eigen headers.
Een mislukte bezorging wordt later opnieuw geprobeerd met dezelfde X-Webhook-ID. Bewaar de waarden die je al verwerkt hebt en sla een verzoek met een bekende waarde over; zo belandt dezelfde lead nooit twee keer in je systeem. Let op het verschil: het veld id in de JSON identificeert het event, X-Webhook-ID identificeert de bezorging aan deze specifieke webhook.
- X-Webhook-Signature-256 — HMAC-SHA256-handtekening van de payload, in de vorm sha256=<hexadecimale waarde>.
- X-Webhook-Event — het eventtype, bijvoorbeeld visitor.form_submission.
- X-Webhook-ID — unieke ID van deze bezorging; gebruik deze om dubbele verwerking te voorkomen.
Handtekening controleren
Elke bezorging is ondertekend met een HMAC-SHA256-handtekening, berekend over de JSON-body met jouw secret als sleutel. Door de handtekening te controleren weet je zeker dat het verzoek echt van LeadBot komt. Verwerk de payload pas na een geslaagde controle en antwoord met status 401 als de controle mislukt.
Bereken de handtekening altijd over de ongewijzigde, ruwe request body en parse de JSON pas daarna: opnieuw serialiseren kan spaties of veldvolgorde veranderen en levert dan een andere handtekening op. Stel je framework zo in dat de raw body beschikbaar blijft en vergelijk met een timing-safe functie, zoals in dit Node.js-voorbeeld.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyLeadBotWebhook(rawBody, signatureHeader, secret) {
const supplied = signatureHeader?.replace(/^sha256=/, "") ?? "";
const expected = createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const suppliedBuffer = Buffer.from(supplied, "hex");
const expectedBuffer = Buffer.from(expected, "hex");
return suppliedBuffer.length === expectedBuffer.length &&
timingSafeEqual(suppliedBuffer, expectedBuffer);
}Snel antwoorden en opnieuw proberen
Antwoord zo snel mogelijk met een statuscode van 200 tot en met 299; elke andere uitkomst telt als mislukt. Wacht niet tot je CRM of een andere trage dienst klaar is: controleer de handtekening, zet het werk in een wachtrij en stuur direct je antwoord.
Mislukt een bezorging door een time-out, netwerkfout of foutstatus, dan probeert LeadBot het standaard nog twee keer: na 5 en na 10 minuten. In totaal zijn dat dus drie afleverpogingen. Op de detailpagina van de webhook pas je dit onder Geavanceerde instellingen aan met de velden Aantal pogingen (0 tot 10, waarbij de eerste poging meetelt) en Time-out per verzoek (1 tot 300 seconden). De wachttijd voor een nieuwe poging is steeds 5 minuten maal het aantal pogingen dat al is gedaan. Omdat een bezorging dus meerdere keren kan binnenkomen, is het belangrijk dat je verwerking idempotent is. Zo bouw je dat op:
- 1Lees de ruwe request body en de headers.
- 2Controleer de handtekening in X-Webhook-Signature-256.
- 3Controleer of je de X-Webhook-ID al eerder hebt verwerkt.
- 4Sla het verzoek op of zet het in een interne wachtrij.
- 5Antwoord met een 2xx-statuscode.
- 6Verwerk de lead daarna verder in je eigen systeem.
Bezorginformatie en problemen oplossen
Open een webhook en ga naar het tabblad Bezorginformatie. Daar zie je per bezorging het event, de status, de HTTP-statuscode, het aantal pogingen en het tijdstip, en waar beschikbaar de response of foutmelding. Bezorglogs worden 30 dagen bewaard.
Mislukken tien afleverpogingen op rij, dan zet LeadBot de webhook op Inactief. Een inactieve webhook blijft nieuwe events ontvangen, maar elk event wordt maar één keer geprobeerd, zonder nieuwe pogingen. Zodra een bezorging weer slaagt, staat de webhook automatisch weer op Actief. Los het probleem dus op en klik op Webhook testen, of wacht op de volgende lead.
Antwoordt je endpoint met 410 Gone, dan gaat LeadBot ervan uit dat het endpoint definitief is opgeheven. De webhook wordt dan meteen uitgeschakeld en ook geplande nieuwe pogingen worden niet meer verstuurd. Een gepauzeerde of uitgeschakelde webhook ontvangt niets; zet hem weer aan als je endpoint terug is. De meest voorkomende problemen:
- 401 of 403 — controleer of je de juiste secret gebruikt en de handtekening over exact de ruwe body berekent.
- 404 — controleer het volledige pad van de URL en of je endpoint POST-verzoeken accepteert.
- Timeout — antwoord eerder met 2xx en verplaats trage verwerking naar een wachtrij.
- 5xx — bekijk je eigen serverlogs; LeadBot probeert het opnieuw zolang er pogingen over zijn.
- Geen bezorging — controleer of de webhook niet gepauzeerd of uitgeschakeld is, het juiste event geselecteerd is en je in de juiste organisatie werkt.
Kom je er niet uit?
Geen ticketnummer — je spreekt altijd een mens. We denken graag met je mee, van installatie tot optimalisatie.
