← Blog

Security

WhatsApp Business agents: BSN-veilige audit checklist

Een Nederlandse DPO reageerde op onze vrijdagdemo met één zin: elke WhatsApp Cloud API payload staat 30 dagen op een US-server. Dit is de audit die wij nu draaien voordat een agent live gaat.

Jacob Molkenboer· Oprichter · A Brand New Company· 17 mei 2025· 8 min
Gesloten manilla map met rode lakzegel, groen lint, koperen slot en kopiebon op ivoorkleurig papier.

De vrijdagmail die onze launch kelderde

Het is 16:48 op een vrijdag in mei. Een WhatsApp-agent voor een Utrechtse zorgadministratie staat gepland om maandagochtend live te gaan. De intake-flow is aangesloten, de kennisbank is geladen, de escalatielogica is getest tegen twintig staging-conversaties. De projectleider bij de klant is tevreden. Dan reageert haar DPO op de demo-thread met één enkele alinea.

"Je realiseert je toch dat elk inbound bericht dat we zojuist via de Cloud API hebben verstuurd, nu dertig dagen op een Meta-server in de Verenigde Staten staat? Inclusief het BSN dat onze testpatiënt tijdens de booking-flow heeft ingetypt."

Ze heeft gelijk. We hadden de agent door een schone staging tenant gedraaid, de prompt-templates opgeschoond, een eigen database in eu-central-1 opgezet en aan onze kant een zorgvuldige audit log gebouwd. Niets daarvan deed ertoe. De WhatsApp Cloud API logt zelf elke payload (het bericht, de media-URL's, de contact-metadata) en bewaart die tot 30 dagen in Meta's US-region storage. De eigen documentatie van Meta is daar duidelijk over als je de data-handling pagina's goed leest. De DPO had dat gedaan.

De launch schoof twee weken op. Wat volgt is de checklist die we nu draaien voordat een chat-agent bij ons een vraag mag beantwoorden die een BSN, een AGB-code of een BIG-registratie kan raken. Hij is met opzet droog. Het interessante werk begint pas nadat de checklist door is.

Het 30-daagse log binnen Meta

De Cloud API is de versie van WhatsApp Business die Meta voor je host. De meeste SDKs, voorbeelden en integratie-gidsen die na eind 2024 zijn geschreven, gaan ervan uit dat je hierop draait, omdat de On-Premises API in oktober 2025 is uitgefaseerd. De trade-off: de message broker, de queue en de tijdelijke storage zitten allemaal binnen Meta's infrastructuur, niet die van jou.

Met elk inbound bericht gebeuren drie dingen voordat jouw webhook ook maar afgaat:

  • De payload wordt ontsleuteld aan Meta's edge zodat hij gerouteerd kan worden.
  • Een kopie wordt weggeschreven naar een request log dat gebruikt wordt voor delivery-bevestiging, retries en misbruikdetectie.
  • De ontsleutelde body wordt tot 30 dagen bewaard voor operationele doeleinden, in een regio die Meta kiest op basis van routing-regels. Voor de meeste van onze klanten komt die regio uit op us-east.

Voor het grootste deel van B2B-chat is dat prima. Een bezorgbevestiging, een verzendvraag, een sales lead met een naam en een e-mailadres: gewone persoonsgegevens met een duidelijke grondslag onder overeenkomst of gerechtvaardigd belang. Voor chat die een burgerservicenummer, een zorgverlenercode of een gereguleerd practitioner-ID citeert, is het een doorgifte van bijzondere persoonsgegevens naar een derde land onder artikel 9 AVG. Schrems II werd in 2025 niet makkelijker. Het EU-US Data Privacy Framework geeft je een grondslag, maar geen schoon audit-antwoord wanneer de Autoriteit Persoonsgegevens belt en vraagt waar het BSN van een specifieke patiënt afgelopen dinsdag tussen 11:02 en 11:03 naartoe ging.

Waarschuwing

Als jouw agent ooit een vrije-tekstveld krijgt dat een BSN kan bevatten, moet je ervan uitgaan dat het er ook in zit. Patiënten plakken uit oude mails. Huisartsen en apothekers ook. Hopen dat de gebruiker "het wel weet te laten" is geen control.

De pre-launch audit

De onderstaande checklist draaien we nu voordat het WhatsApp business-nummer wordt geverifieerd, voordat de webhook naar productie wordt gewezen en voordat de agent aan een echte patiënt of echte klant wordt getoond. Elke stap heeft een ja/nee antwoord en een eigenaar bij naam. Als een stap "nee" is, gaat de agent niet live.

1. Inventariseer elke gereguleerde identifier die de agent kan zien

Bij een Nederlandse zorgklant zijn dat: BSN (9 cijfers, gevalideerd via de 11-proef), AGB-code (8 cijfers), BIG-registratie (11 cijfers), DigiD-gebruikersnamen, UZI-nummers van de ZIN en elke combinatie van naam plus geboortedatum plus postcode (die samen identificerende persoonsgegevens vormen, ook zonder uniek nummer). Bij een Nederlandse financiële klant: IBAN, BSN, RSIN en KvK-nummers die aan een natuurlijk persoon gekoppeld zijn. De lijst gaat aan de muur in het projectkanaal.

2. Leg op papier vast wat de agent met elk type mag doen

Meestal is het antwoord "niets". De agent mag een BSN niet teruggeven, niet opslaan in een eigen database, niet doorgeven aan de LLM-provider en niet meesturen in een analytics-event. De beslismatrix staat in een document van één pagina dat de DPO tekent voordat de integratie begint. Zonder dat document maakt het engineering-team beleidsbeslissingen, en dat is de verkeerde default.

3. Plaats een redactor voor Meta, niet erachter

Dit is de hefboom die de meeste teams missen. Je kunt Meta er niet van weerhouden te loggen wat de gebruiker heeft verstuurd. Je kunt de gebruiker er wel van weerhouden het überhaupt te versturen, door het chatoppervlak voor die flows te vervangen door een eenmalige link naar een Nederlands gehost webformulier. We komen verderop terug op de implementatie.

4. Zet de webhook-ontvanger op slot

De webhook-ontvanger is de eerste verdedigingslinie binnen je eigen perimeter. Hij moet binnen de EU draaien (wij gebruiken eu-central-1 in Frankfurt of eu-west-1 in Ierland), hij moet gereguleerde identifiers redacteren voordat er iets anders wordt weggeschreven, en hij moet de ruwe body uit elke error-reporting tool slopen voordat Sentry of Datadog een kopie krijgt. Sentry's default-configuratie stuurt een BSN moeiteloos naar zijn US-servers als onderdeel van een stack trace. Test daar expliciet op met een nep-payload tijdens de integratie, niet tijdens de incident review.

5. Verifieer het LLM call-pad

Als jouw agent het bericht van de gebruiker doorstuurt naar een LLM die buiten de EU verwerkt, maak je dezelfde grensoverschrijdende doorgifte twee keer. De meeste providers bieden inmiddels EU-region inference endpoints, maar het default endpoint in de SDK staat nog steeds op US. Hard-code het EU-endpoint, pin het vast in CI met een test die faalt als de URL verandert, en documenteer de regio in het verwerkingsregister.

6. Beperk de bewaartermijn aan jouw kant

Je eigen logs zijn een parallel risico. Wij bewaren WhatsApp-berichtbodies maximaal zeven dagen in onze eigen systemen, en BSN-achtige identifiers worden voor opslag gemaskeerd. Alles wat ouder is, leeft als niet-persoonlijke teller (timestamp, gesprekslengte, uitkomstbucket). Zeven dagen is genoeg om een issue te debuggen dat op maandag is gemeld. Het is niet genoeg om een gedragsprofiel op te bouwen, en dat is precies de bedoeling.

7. Maak de escalatieroute de default voor grijze gevallen

Elke flow die een gereguleerde identifier kan raken, heeft een harde uitgang naar een mens of naar een Nederlands gehost formulier. De agent moet weten hoe hij "deze vraag moet via ons beveiligde portaal" zegt voordat de gebruiker het nummer intypt. Train de prompt met voorbeelden. Test hem met adversariële berichten waarin de gebruiker het BSN ongevraagd geeft.

Het redactie-proxy patroon

De technische stap die het meeste werk doet, is een kleine redactie-proxy tussen de WhatsApp-webhook en al het andere. Elk inbound bericht raakt deze eerst. Hij herschrijft gereguleerde identifiers naar tokens voordat een downstream systeem (LLM, logger, database, ticketing-tool, error tracker) de body te zien krijgt.

De minimum viable versie is ongeveer dertig regels TypeScript:

// proxy/redact.ts
const PATTERNS = {
  bsn: /\b(\d{9})\b/g,
  agb: /\b(\d{8})\b/g,
  big: /\b(\d{11})\b/g,
  iban: /\b([A-Z]{2}\d{2}[A-Z0-9]{10,30})\b/g,
};

function isValidBsn(d: string): boolean {
  if (d.length !== 9) return false;
  const w = [9, 8, 7, 6, 5, 4, 3, 2, -1];
  const sum = d.split('').reduce((a, c, i) => a + Number(c) * w[i], 0);
  return sum % 11 === 0;
}

export function redact(text: string): { clean: string; hits: string[] } {
  const hits: string[] = [];
  let clean = text;

  clean = clean.replace(PATTERNS.bsn, (m) => {
    if (!isValidBsn(m)) return m;
    hits.push('bsn');
    return '[BSN_REDACTED]';
  });

  for (const [label, re] of Object.entries(PATTERNS)) {
    if (label === 'bsn') continue;
    clean = clean.replace(re, () => {
      hits.push(label);
      return `[${label.toUpperCase()}_REDACTED]`;
    });
  }
  return { clean, hits };
}

De 11-proef op het BSN doet ertoe. Zonder die check wordt elk Nederlands telefoonnummer, elke 9-cijferige factuurregel en elke willekeurige tracking-code opgevangen, en wordt de agent onbruikbaar. Met de check vang je alleen strings die wiskundig geldige BSNs zijn. De Wbsn-definitie is wat de validator implementeert; verzin geen eigen versie.

Wat je met de hits-array doet, is de tweede helft. In onze flow dwingt elke niet-lege hits-array de agent om te reageren met een vaste tekst: "Ik zie dat deze vraag persoonsgegevens bevat die we niet via WhatsApp kunnen verwerken. Open dit beveiligde formulier om verder te gaan: [link]". De link is een eenmalige URL die een Nederlands gehost formulier opent, de intake daar afrondt en het resultaat naar onze eigen EU-database wegschrijft. Meta krijgt het gevalideerde BSN nooit te zien, en jouw downstream LLM ook niet.

Kernpunt

Je kunt Meta er niet van weerhouden te loggen wat binnenkomt. Je kunt de gebruiker er wel van weerhouden het te versturen. De redactie-proxy is precies wat je die tweede mogelijkheid oplevert.

Overdragen aan een mens, of aan een formulier

De moeilijkere ontwerpvraag is wanneer je overdraagt aan een mens en wanneer aan een formulier. Het juiste antwoord hangt af van wat de gebruiker probeert te doen.

Bij "ik wil een intake boeken" verzamelt de agent alles wat geen gereguleerde identifier is (de reden van het bezoek, de voorkeursdatum, de status van de verwijzing) en draagt vervolgens over aan een Nederlands gehost bookingformulier dat als allerlaatste stap om het BSN vraagt. De submit van het formulier post naar onze EU-database, de database meldt het bij de praktijk, en de patiënt krijgt van de agent een WhatsApp-bevestiging die het nummer niet teruggeeft.

Bij "wat was mijn AGB-code ook alweer, ik ben de mail kwijt" probeert de agent het niet eens. Hij draagt over aan een mens, punt, want dat is een identiteitsverificatie verkleed als opzoekvraag. Dat hebben we in 2025 op de harde manier geleerd tijdens een pilot, waar een beleefde agent de code hulpvaardig teruglas aan iemand die uiteindelijk niet de practitioner bleek te zijn.

Bij "is de BIG-registratie van Dr X nog geldig" bestaat er een openbaar register (het BIG-register) en kan de agent met een naamzoekopdracht een openbaar record bevestigen zonder ooit om het nummer te vragen. Leun op openbare data waar die bestaat. Het schoonste antwoord is het antwoord dat de gereguleerde identifier helemaal niet nodig heeft.

Het proactieve-agent probleem

De trend aan de AI-kant deze maand is dat agents proactiever worden. De voorpagina van Hacker News op de ochtend dat we dit schreven, had drie aparte threads over agents die buiten hun instructies handelen, waaronder eentje die zijn operator 's nachts in de min wist te helpen door behulpzaam te willen zijn. Dat is precies het faalpatroon waar deze audit tegen bestand is. Een proactieve agent die zelfstandig besluit om "ontvangst te bevestigen" door het BSN van de patiënt terug te citeren in de chat, is een AVG-inbreuk met een publieke chatlog. De redactie-proxy en de no-echo regel zorgen ervoor dat de agent die fout letterlijk niet kan maken, ongeacht welk model erin draait. Saaie guards verslaan slimme prompts.

De vijf-minuten audit die je vandaag kunt doen

Kies je meest blootgestelde chatoppervlak. Open de webhook-handler in je editor. Zoek in de source naar de strings BSN, AGB, BIG en IBAN. Als de zoekopdracht nul resultaten oplevert, weet jouw code niet eens dat die identifiers bestaan, wat betekent dat niets ze maskeert voordat ze bij je logger, je LLM-provider of je error tracker terechtkomen. Dat is je startpunt voor maandag.

Toen wij de WhatsApp-intake-agent bouwden voor een Utrechtse zorgadministratie, lekte de standaard Cloud API-integratie het BSN van de eerste patiënt rechtstreeks in onze error tracker bij het eerste mislukte bericht in staging. We hebben dat opgelost door een redactie-proxy voor alle andere systemen te zetten en de gereguleerde helft van het gesprek naar een Nederlands gehost formulier te routeren voordat een nummer ooit Meta bereikte. Dat is het AI-agents patroon dat we nu standaard hergebruiken.

Kern

Je kunt Meta er niet van weerhouden WhatsApp-payloads te loggen. Je kunt de gebruiker er wel van weerhouden een BSN te sturen, door te redacteren voor de proxy en de gereguleerde helft van het gesprek naar een Nederlands gehost formulier te routeren.

FAQ

Kunnen we de WhatsApp Cloud API überhaupt gebruiken voor AVG-gereguleerde workflows?

Ja, als je gereguleerde identifiers redacteert voordat de gebruiker ze verstuurt, of die uitwisselingen helemaal buiten WhatsApp om route. De Cloud API geeft op zichzelf geen schoon audit-antwoord voor BSN-achtige data.

Lost het EU-US Data Privacy Framework de BSN-doorgifte op?

Het biedt een juridische grondslag, maar geen operationele. Je moet nog steeds aantonen dat je dataminimalisatie toepast. Het framework ontslaat je niet van de plicht om uit te leggen waarom een BSN überhaupt een grens overging.

Wat is er met de WhatsApp On-Premises API gebeurd?

Die is in oktober 2025 uitgefaseerd. Nieuwe projecten moeten de Cloud API of een Business Solution Provider gebruiken. Geen van beide haalt je verplichting weg om gereguleerde identifiers te redacteren voordat je logt.

Hoe lang bewaart Meta berichten nu echt?

Op het moment van schrijven tot 30 dagen in operationele logs. Bewaartermijnen veranderen, dus check de developer-documentatie opnieuw voor elke grote launch en leg het antwoord vast in je DPIA.

Kunnen we logging aan onze kant gewoon uitzetten en klaar?

Dat dekt jouw helft van het risico af. Meta's helft staat buiten je macht. Alleen het proxy-patroon, dat voorkomt dat het BSN überhaupt bij Meta aankomt, dicht beide gaten.

ai agentschat agentssecurityautomationintegrationsarchitecture

Iets bouwen?

Start een project