← Blog

Automation

Hybride CRM-audit: de checklist vóór een sales-agent

Drie cijfers bepalen of je hybride CRM een sales-agent aankan: duplicate drift op de top-25 pipelines, label coverage op de top-10 stages, en één test van 36 uur.

Jacob Molkenboer· Oprichter · A Brand New Company· 21 jun 2026· 10 min
Groen leren onderlegger met messing checklistkaart, messing relais, pneumatische buis, limegroen briefje, rode lakzegel.

Het is 09:14 op een dinsdag in Hilversum. We zijn drie dagen verwijderd van een offerte voor een sales-agent-retrofit bij een B2B SaaS-bedrijf dat ongeveer €11M ARR draait. Op het linker scherm staat een deal op Stage 4 in Salesforce: Acme B.V., €48k, verwachte close Q3, eigenaar een account exec die in mei begon. Op het rechter scherm staat hetzelfde logo — geschreven als "Acme BV", zonder punt — in HubSpot, drie weken geleden closed-won gemarkeerd, eigenaar een sales rep die in februari is vertrokken. Beide records hebben het consent-vinkje aangevinkt. Geen van beide tijdstempels komt overeen.

Dit is het moment waarop de audit zichzelf terugverdient. Als we de retrofit nu offreren, leest de agent die we opleveren beide records als waarheid en begint hij het verkeerde contact aan de verkeerde kant te mailen, met een consent-tijdstempel die een toegangsverzoek wel of niet overleeft. Dus voordat we offreren, doen we een audit van vier uur op de data van de verkoper. Iedere keer dezelfde checklist. We offreren niet zonder.

Waarom de audit vóór de offerte komt

Een sales-agent erft alles wat het CRM hem vertelt. Bij een schone setup met één systeem is dat een hanteerbaar probleem — slechte data doet de agent pijn, de agent maakt de slechte data zichtbaar, je fixt het in een week. Bij een HubSpot/Salesforce-hybride die twee jaar onbeheerd draait, is de slechte data niet alleen slecht. Hij is asymmetrisch. De ene kant gelooft het ene, de andere kant het andere, en de agent kiest het record dat hij als eerste opvroeg.

Iedereen die dit jaar agents in productie zet komt op dezelfde conclusie uit: betrouwbaarheid is geen modelprobleem, het is een state-probleem. Agents zijn betrouwbaar voor zover de wereld die ze lezen consistent is. Hybride CRM's zijn waar consistentie stilletjes sterft, om 04:15, op een dinsdag, zonder log-retentie.

De audit controleert dus drie dingen in deze volgorde: duplicate-contact drift over de top-25 pipelines, dekking van association-labels op de top-10 deal-stages, en of drie specifieke workflows een sync-pauze van 36 uur overleven zonder het AVG-verplichte consent-spoor te verliezen. Als één van de drie faalt, offreren we de retrofit niet. We offreren eerst apart een reconcile, zodat de verkoper kan kiezen of hij dat werk intern oppakt of met ons.

Duplicate-contact drift op de top-25 pipelines

'Drift' is het aandeel contacten in een pipeline dat aan beide kanten bestaat maar het oneens is over iets waar de agent om geeft: e-mail, primair telefoonnummer, account owner, lifecycle stage of consent-tijdstempel. We berekenen het pipeline-voor-pipeline, omdat de top-25 pipelines ongeveer 90% van het werk van de agent doen en één globaal driftcijfer de slechte verbergt. Een gezond gemiddelde van 8% globaal is vaak 4% op de kleine pipelines en 31% op die ene pipeline die daadwerkelijk omzet boekt.

Het script is met opzet saai. Haal contacten op via beide API's, normaliseer e-mails (lowercase, plus-aliassen eraf, punten in Gmail-adressen eraf), en join.

// audit/drift.mjs
import { fetchHubspotContacts, fetchSalesforceContacts } from './crm.mjs'
import { levenshtein } from './util.mjs'

const norm = (e) => e.toLowerCase().replace(/\+[^@]+/, '').trim()

for (const pipeline of TOP_25_PIPELINES) {
  const hs = await fetchHubspotContacts({ pipeline })
  const sf = await fetchSalesforceContacts({ pipeline })

  const hsByEmail = new Map(hs.map((c) => [norm(c.email), c]))
  let exact = 0, fuzzyHits = 0, orphans = 0

  for (const c of sf) {
    const match = hsByEmail.get(norm(c.email))
    if (match && match.owner === c.owner) exact++
    else if (match) fuzzyHits++
    else orphans++
  }

  const drift = ((sf.length - exact) / sf.length) * 100
  console.log(`${pipeline}: drift=${drift.toFixed(1)}%, orphans=${orphans}`)
}

De drempels die we hanteren, geleerd uit een dertigtal van deze audits bij Nederlandse mkb'ers:

  • Onder 12% drift op een top-25 pipeline: de agent kan gerefit worden, met een lichte opschoonronde op de ergste gevallen in week één.
  • 12–25%: stoplicht. We offreren een reconcile van twee weken vóór de retrofit, en we rekenen die apart zodat de verkoper kan kiezen of hij het zelf doet.
  • Boven de 25%: de agent gaat ownership en consent hallucineren. We weigeren de retrofit tot de verkoper een system of record kiest voor die pipeline. Dat gesprek duurt meestal langer dan het technische werk.

Eén ding dat het script hierboven niet vangt is owner drift. De owner van een contact kan stringmatig kloppen en alsnog fout zijn — als de AE die hem bezit in januari vertrok en HR de mailbox nooit heeft afgesloten, dan zet de agent berichten in de wachtrij 'van' een adres dat niemand leest. Vóór week één van de retrofit checken we de owner-kolom kruislings tegen de uitstroomlijst uit het HRIS van de verkoper. Niemand schrijft die join graag. Hij vangt ruwweg 6% van de contacten in elke audit die we hebben gedaan, en bij één bijzonder slechte verkoper was het 19%.

Dekking van association-labels op de top-10 deal-stages

De association labels van HubSpot (Decision Maker, Champion, Influencer, Billing Contact, Procurement) zijn het dichtst dat een CRM bij 'wie ertoe doet op deze deal' komt. Salesforce noemt ze Contact Roles op de Opportunity. Een sales-agent gebruikt deze labels om te bepalen met wie hij opent, wie in CC gaat en wie hij nooit mailt zonder dat de AE het zegt. Zonder ze pakt de agent het laatst aangeraakte contact en hoopt het.

De audit telt, per deal-stage in de top-10, het aandeel deal-contact associaties dat aan een van beide kanten een label draagt:

-- audit/labels.sql
SELECT
  ds.name AS stage,
  COUNT(*) FILTER (WHERE a.label IS NOT NULL) * 1.0
    / COUNT(*) AS coverage
FROM deal_stages ds
JOIN deal_contact_associations a USING (deal_id)
WHERE ds.id IN (SELECT id FROM TOP_10_STAGES)
GROUP BY ds.name
ORDER BY coverage ASC;

Onder 60% dekking op een late-funnel stage — proposal, contract-out, verbal — mailt de agent de verkeerde persoon op de verkeerde dag. We hebben een agent op niet-opgeschoonde data een renewal-prompt zien sturen naar de voorganger van de contractondertekenaar, die achttien maanden geleden vertrok. Het adres resolvete nog via een forwarding-regel, de e-mail belandde in niemands inbox, de deal werd een kwartaal stil, en de post-mortem gaf de agent de schuld. De agent deed precies wat zijn data hem opdroeg.

Let op

Een globale label-dekking van 90% verbergt een dekking van 40% op je contract-out stage. Score per stage. Nooit als één cijfer.

De sync-pauze van 36 uur om 04:15

Maart 2025, een klant in Utrecht. Hun integratietool — een populaire Make.com-vervanger — pauzeerde stilletjes de HubSpot→Salesforce-queue om 04:15 op een dinsdag nadat een auth-token roteerde en niemand de connector bijwerkte. De queue stapelde 36 uur op voordat iemand in de standup opmerkte dat een deal niet was opgeschoven. Tegen de tijd dat we werden gebeld, hadden er 312 consent-statuswijzigingen plaatsgevonden aan de HubSpot-kant zonder door te lopen: nieuwe inschrijvingen, twee uitschrijvingen, zes expliciete double opt-ins geregistreerd via een webformulier dat aan een download gekoppeld was.

De AVG-vraag landde twee weken later, toen een contact een Artikel 15-toegangsverzoek indiende: laat zien wanneer ik toestemming heb gegeven, op welk kanaal, en wat de formuliertekst op dat moment zei. De middleware-logs waren al weg — zeven dagen retentie, standaard. HubSpot had één tijdstempel. Salesforce had een andere, geschreven toen de queue donderdagmiddag leegliep. De formuliertekst op het webformulier was er tussendoor aangepast door een marketingstagiair. Niemand kon bewijzen met welke versie het contact daadwerkelijk had ingestemd.

De boete die de toezichthouder onder de AVG theoretisch kan opleggen loopt op tot €20M of 4% van de jaaromzet, welk bedrag hoger is. In de praktijk komt de Nederlandse Autoriteit Persoonsgegevens zelden in de buurt van dat plafond — maar één klacht trekt zes weken operator-tijd in bewijsverzameling. De middleware-logs waar ze om vragen zijn, in onze ervaring, weg tegen de tijd dat ze ernaar vragen. Bouw alsof ze er niet zijn.

Dit is de test die we op elke workflow in de audit toepassen: als de sync 36 uur pauzeert vanaf 04:15, kun je dan voor elke gebeurtenis in dat venster het consent-spoor reconstrueren, met tijdstempels en de exacte tekst die het contact zag? De meeste workflows zakken. Drie patronen overleven.

Drie workflows die de sync-pauze overleven

Consent-capture schrijft eerst naar een ledger

Consent gaat nooit rechtstreeks naar HubSpot of Salesforce. Het gaat naar een derde plek — een kleine append-only Postgres-tabel — met een server-side tijdstempel, de gerenderde formuliertekst bevroren op het moment van capture (geen verwijzing naar een template die volgende week kan veranderen), het IP en de user agent. HubSpot en Salesforce worden allebei downstream readers. Als de sync pauzeert, blijft de ledger onaangeroerd en is het AVG-spoor intact, ongeacht welke kant nog draait.

-- consent ledger schema
CREATE TABLE consent_events (
  id            BIGSERIAL PRIMARY KEY,
  contact_email TEXT        NOT NULL,
  event_type    TEXT        NOT NULL, -- 'opt_in', 'opt_out', 'doi_confirmed'
  channel       TEXT        NOT NULL, -- 'webform', 'api', 'manual'
  form_text     TEXT        NOT NULL, -- frozen at capture
  ip            INET,
  user_agent    TEXT,
  occurred_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  source_system TEXT        NOT NULL  -- 'webform-v3', 'hubspot-form', ...
);
-- no UPDATE, no DELETE, ever. revoke them at the role level.

Stage-overgangen triggeren durable jobs, geen webhooks

Een deal die naar 'Contract Out' beweegt zou een contract-verzending moeten triggeren. Als je dat aan elkaar knoopt met een Zapier-webhook of een in-CRM workflow die POST't naar je contracttool, betekent een sync-pauze dat de trigger twee keer afgaat, of nooit, of tegen de verkeerde stage als de queue leegloopt. Wij zetten de trigger in een durable job runner (Inngest of Temporal werken allebei) met een idempotency key van deal_id:to_stage_id:transition_timestamp. Als de sync hervat en de trigger een tweede keer binnenkomt, ziet de job runner de key en doet niks. Het contract van de agent gaat één keer de deur uit, met een tijdstempel die het systeem kan verdedigen.

Wat de durable runner je nog meer oplevert is een replay-log. Zes weken nadat de agent live gaat, vraagt iemand op juridische zaken waarom een bepaald contract op woensdag om 16:42 de deur uit ging. Met een echte job runner open je het deal_id in het dashboard en zie je de trigger-payload, de stage op het moment van de trigger, en de exacte prompt die de agent eromheen heeft samengesteld. Zonder dat is het antwoord een schouderophalen, en een schouderophalen is wat een vraag in een audit verandert.

Unsubscribe-checks lezen bij elke verzending uit de ledger

Het verleidelijke ontwerp is om unsubscribes elke vijf minuten tussen HubSpot en Salesforce te syncen. Het broze ontwerp ook. Tijdens de pauze van 36 uur krijgt een uitgeschreven contact alsnog de volgende campagne van de kant die de update nog niet had — en dat is een AP-klacht die staat te wachten. In plaats daarvan: elke check op het moment van verzenden — die van de agent, van de marketingtool van HubSpot, van de outbound van Salesforce — slaat onderweg direct aan op de consent-ledger. Misschien 40ms trager. Kan niet fout zijn.

Kernpunt

Het consent-spoor hoort in een systeem dat niet synct. Op het moment dat je het de taak van de CRM's maakt om het over consent eens te worden, hangt je AVG-compliance af van uptime.

Wat de audit teruggeeft

De audit eindigt met een PDF van één pagina. Drie cijfers bovenaan: gemiddelde drift op top-25 pipelines, gemiddelde label-dekking op top-10 stages, en een pass/fail per overlevende workflow. Daaronder een lijst van pipelines en stages gerangschikt op reconcile-werk, met de drie ergste gevallen bij naam genoemd. We markeren de agent-retrofit als ready, ready-after-reconcile, of not-yet.

De lijst is met opzet saai. Elke rij benoemt een pipeline of stage, het huidige drift- of dekkingscijfer, de in euro's gewogen waarde van de deals die nu in die bucket zitten, en een schatting van de uren om te reconcilen. We sorteren op deal-waarde-op-het-spel, niet op driftpercentage — 35% drift op een pipeline die zes deals per jaar draait weegt minder dan 14% drift op de pipeline die er vierhonderd draait. De RevOps-eigenaar leest het, kiest een top drie, en wij offreren dat werk of geven de lijst terug zodat het interne team het op eigen tempo aanvalt.

Not-yet is ruwweg één op de vijf verkopers. Ze horen het niet graag. Ze vinden het nog minder leuk als zes maanden later iemand anders zijn agent de voormalig assistent van hun procurement-contact heeft gemaild over een renewal, en het antwoord een beleefd-maar-stevig AVG-verzoek is van het juridische team van het contact.

Toen wij de sales-agent bouwden voor een B2B SaaS-bedrijf in Hilversum dat precies deze hybride draaide, kwam de duplicate-contact drift uit op 31% op de top-pipeline en de label-dekking op 44% op contract-out. We deden een reconcile van een week, zetten de consent-ledger op, en pas daarna begonnen we prompts te schrijven. De plumbing onder elke AI-agent die we opleveren is het stuk waar niemand om vraagt, totdat ze ooit zonder zijn gelanceerd.

Iets van vijf minuten dat je vandaag kunt doen: open je Salesforce-rapporten, draai een contact-by-email duplicate-rapport tegen je top-pipeline, en deel het aantal unieke e-mails door het totaal aantal contacten. Als de uitkomst onder 0,88 ligt, zit je sales-agent fout vóór hij de eerste e-mail verstuurt.

Kern

Het consent-spoor hoort in een systeem dat niet synct — op het moment dat de CRM's het over consent eens moeten worden, hangt je AVG-compliance af van uptime.

FAQ

Wat telt als 'drift' tussen HubSpot en Salesforce?

Drift is het aandeel contacten dat aan beide kanten bestaat maar het oneens is over e-mail, owner, telefoon, lifecycle stage of consent-tijdstempel. We scoren per pipeline, niet globaal, want de slechtste pipelines verbergen zich achter gezonde gemiddelden.

Waarom label-dekking per deal-stage scoren in plaats van globaal?

Een globale dekking van 90% kan een dekking van 40% op contract-out verbergen, en juist daar kost het verkeerde contact je de deal. Late-funnel stages bepalen met wie de agent opent, dus die hebben hun eigen cijfer nodig.

Hoe helpt een consent-ledger bij de AVG?

Hij legt consent vast in een derde systeem dat niet van CRM-sync afhangt. Tijdstempels, formuliertekst en kanaal worden bevroren op het moment van capture, dus een Artikel 15-toegangsverzoek kan beantwoord worden ook als HubSpot en Salesforce het 36 uur oneens zijn.

Waarom niet gewoon één CRM kiezen in plaats van een hybride draaien?

De meeste Nederlandse mkb'ers onder de €16M kunnen halverwege het jaar geen migratie verantwoorden, en sales en marketing zijn het zelden eens over welke kant ze houden. De audit doe je als de hybride de realiteit is, niet het doel.

Hoe lang duurt de audit?

Ongeveer vier uur scripttijd op de data van de verkoper plus een halve dag om de output door te nemen met de RevOps-eigenaar. We leveren een PDF van één pagina met drie cijfers en een gerangschikte reconcile-lijst.

ai agentsautomationprocess automationintegrationsoperationsstrategy

Iets bouwen?

Start een project