← Blog

Voice agents

Voice agent voor een Tilburgse uitvaartonderneming: playbook

Een nabestaande dochter belt om 23:14 een Tilburgse uitvaartonderneming. De voice agent heeft 45 seconden om te beslissen of dit nu een mens nodig heeft, en de 36-uursklok loopt al.

Jacob Molkenboer· Oprichter · A Brand New Company· 12 mrt 2026· 11 min
Zwarte bakelieten hoorn op leren onderlegger, ivoren kaart met groen lint, koperen zakhorloge, gedroogde rozemarijn.

Het is 23:14 op een dinsdagavond in november. Een vrouw in Goirle belt het centrale nummer van een uitvaartonderneming in Tilburg. Haar vader is veertig minuten geleden overleden in het hospice. Ze weet niet wat ze nu moet doen, alleen dat iemand haar dit nummer heeft gegeven. De intake-medewerker is om 18:00 naar huis gegaan. De dienstdoende uitvaartverzorger rijdt terug van een uitvaart in Eindhoven en kan pas over zeven minuten opnemen.

Hier neemt de voice agent op.

Het kader van beperkingen

Vijf beperkingen bepaalden de vorm van het systeem voordat we een regel code schreven.

Ten eerste, de wettelijke klok. De Nederlandse Wet op de lijkbezorging stelt een harde grens van 36 uur tussen overlijden en de start van transport en lijkverzorging, met verlengingsprocedures die niemand om middernacht wil invullen. Die klok loopt al voor de familie ons belt. Tegen de tijd dat de telefoon gaat, kunnen we al zes of acht uur verder zijn.

Ten tweede, het volume. De onderneming waarvoor we dit hebben gebouwd, verwerkt 940 nabestaandengesprekken in een gemiddelde week. Ongeveer 31% komt buiten kantooruren binnen. De nachtdienst was vroeger één dienstmobiel, één moeie uitvaartverzorger en een overdrachtsnotitie de volgende ochtend.

Ten derde, het dossiersysteem. Uitvaartsoftware.nl, een 13 jaar oude SaaS die het merendeel van de kleinere Nederlandse uitvaartondernemingen aandrijft, met een SOAP-achtige API geschreven toen SOAP nog modern voelde. Lezen gaat prima. Een nieuw dossier schrijven vraagt drie round-trips en een bevestigingstoken dat in 90 seconden verloopt.

Ten vierde, het archief. Elke condoleance, de medelevende berichten die een familie ontvangt in de dagen na een overlijden, zit in een zelfgebouwde Exchange 2016 public folder-structuur die de onderneming in 2017 bouwde en die niemand meer heeft aangeraakt sinds de IT-lead vertrok. EWS werkt nog steeds. OAuth niet.

Ten vijfde, regionale spraak. Tilburg en West-Brabant delen Brabantse dialectmarkers die een standaard Nederlands speech-to-text-model in acht tot twaalf procent van de gevallen verkeerd classificeert. De agent moet om kunnen gaan met 'unnen' en 'hedde' en de zachte G die terugkomt zodra een beller zijn waakzaamheid laat zakken, zonder ooit de familie te vragen iets te herhalen onder de zwaarste omstandigheden van hun leven. We hebben de ASR fine-getuned op ongeveer 60 uur geredigeerde opnames uit het archief van de onderneming voordat we hem ook maar één live gesprek lieten beantwoorden.

De triagebeslissing

De eerste regel die we opschreven is de enige die echt telt: dit is een voice agent die weet wanneer hij moet stoppen met voice agent zijn.

We classificeren elk gesprek binnen de eerste 12 seconden. De openingszin van de agent is bewust nietszeggend: een naam, een bevestiging dat dit het juiste nummer is, en één open vraag. Terwijl hij praat, gaat de audiostream al door een parallelle classifier die op drie dingen let.

Eén: acute distressmarkers — aanhoudend huilen, onregelmatige ademhaling, zinnen die midden in een woord stoppen en niet verdergaan. Twee: signalen van suïcide-context — expliciete vermelding door de beller, dubbelzinnige formuleringen over hoe het overlijden plaatsvond, het woord 'zelf' in de buurt van 'gedaan' of 'gevonden', elke verwijzing naar een actuele bedreiging voor de eigen veiligheid van de beller. Drie: tijdkritische operationele verzoeken — de beller is op de plaats van het overlijden en heeft direct advies nodig over wat ze niet moeten aanraken voordat de schouwarts arriveert.

Bij één van die signalen stopt de agent met triëren. Hij zegt één zin: 'Ik verbind u nu door met een uitvaartverzorger, blijft u alstublieft aan de lijn.' Het gesprek staat binnen 45 seconden op de headset van een mens, inclusief de tijd om door het oproeprooster heen te bellen.

Het budget van 45 seconden is niet willekeurig. Het is de langste duur die we in onze pilotweek hebben gezien voordat een ontredderde beller ophing omdat hij dacht dat er niemand zou komen.

De prosodische kenmerken die de classifier voeden, zijn bewust smal. We halen toonhoogtevariatie over een glijdend venster van vier seconden eruit, de pauzelengte binnen woorden en een ruwe score voor ademhalingsonregelmatigheid uit het hoogdoorlaat-gefilterde residu. We draaien geen sentimentanalyse. We draaien geen emotion detection in de marketingbetekenis van dat woord. Beide presteren slechter dan toeval als de spreker in shock is. De signalen die werken, zijn mechanisch: hoe de stem beweegt, niet wat een model denkt dat de stem voelt.

Hieronder de classificatie-aanroep, vereenvoudigd. De productieversie routeert via drie parallelle modellen voor redundantie.

async def classify_turn(audio_chunk, transcript_so_far, call_state):
    classifier_input = {
        "transcript": transcript_so_far,
        "elapsed_ms": call_state.elapsed_ms,
        "prosody": extract_prosody(audio_chunk),
    }
    verdict = await classifier.run(classifier_input)
    if verdict.suicide_context or verdict.acute_distress:
        await escalate_to_uitvaartverzorger(
            call_state,
            reason=verdict.primary_signal,
            transcript=transcript_so_far,
            urgency="immediate",
        )
        return TurnAction.HANDOFF
    if verdict.scene_present:
        return TurnAction.HANDOFF_OPERATIONAL
    return TurnAction.CONTINUE
Waarschuwing

Het lastigere probleem is de false negative: een beller die rustig, articulaat en zakelijk een overlijden beschrijft dat we hadden moeten markeren. We laten de classifier overhellen naar over-escalatie en accepteren een 4% false-positive rate, want de tolerantie voor gemiste gevallen is nul. Het team uitvaartverzorgers ging hiermee akkoord voordat we live gingen.

Onze overdrachtsmetadata bevat ook een verwijzing naar de relevante landelijke context. Voor Nederlandse bellers ziet de dienstdoende uitvaartverzorger één regel met een verwijzing naar de 113-protocollen in de briefingkaart, zodat de mens die het gesprek overneemt de juiste kadering klaar heeft voordat hij iets zegt.

Koppelen aan een 13 jaar oud dossiersysteem

De API van Uitvaartsoftware.nl werkt prima zolang je hem behandelt als een ERP uit 2012, niet als een service uit 2026. Vier dingen die we de harde manier leerden.

Het sessietoken verloopt snel. Op papier dertig minuten. In de praktijk sterft hij ergens tussen de acht en twaalf. We houden een refresh loop draaiend die elke vier minuten loopt, ongeacht of we de verbinding gebruiken.

Dossiers aanmaken is tweefasig. Je POST een concept met de naam van de overledene en de datum van overlijden, daarna PATCH je de rest. Faalt de PATCH, dan blijft het concept voor altijd staan en vervuilt de zoekindex. We wikkelen elke create in een try/finally die wezen verwijdert als het misgaat.

async def create_dossier(payload):
    draft = await uitvaart.post_draft({
        "naam_overledene": payload.naam,
        "datum_overlijden": payload.datum_overlijden,
    })
    try:
        return await uitvaart.patch_dossier(draft.id, payload.full())
    except Exception:
        await uitvaart.delete_draft(draft.id)
        raise

De derde valkuil is dat het BSN-veld, als het aanwezig is, encrypted at rest staat met een sleutel die elk kwartaal roteert. Terug uitlezen vereist de huidige sleutelversie. We laten de voice agent nooit een BSN voorlezen of bevestigen. Wil de familie het delen, dan zegt de agent: 'Stuur het via de beveiligde link die u zo per SMS ontvangt.' Een aparte one-time-link-service handelt de capture af, buiten het transcriptpad van de agent.

De vierde valkuil kostte ons een week om te vinden. Na de elfde write binnen 60 seconden begint de server 200 OK terug te geven met een lege body en gooit hij de payload geruisloos weg. We ontdekten dit door een smoke test die 50 records pushte en de retrieval controleerde; er ontbraken er acht, zonder een fout in de response. We zetten dossier-writes nu in de wacht via een token bucket die afgekapt is op zes per minuut, en we nemen de latency voor lief. Onder piekbelasting piekt de queue rond veertien; niets aan een wachttijd van 90 seconden om een dossierregel te committen schaadt een gesprek buiten kantooruren.

Het Exchange 2016-condoleancearchief

Dit is het stuk dat we wilden weigeren en uiteindelijk bouwden. Elke uitvaart genereert tientallen condoleanceberichten: e-mails, contactformulier-inzendingen, getranscribeerde voicemails. De familie wil ze gearchiveerd hebben en na de dienst in een boekje afgedrukt zien. Het bestaande systeem propt ze allemaal in Exchange public folders, geordend op dossiernummer, zonder consistente metadata.

We migreerden niet. De hoofd-engineer bij de onderneming werkt er al elf jaar en vroeg ons letterlijk om dat niet te doen. Ze hebben een institutioneel spiergeheugen voor waar dingen leven en een control panel uit het Drupal-tijdperk dat netjes aansluit op de mappenboom.

De voice agent schrijft dus naar Exchange via EWS over basic auth, beperkt tot één service-account dat alleen het recht 'create item in named public folder' heeft. Microsofts documentatie staat nog steeds online, als je je weet te oriënteren.

De truc is dat EWS HTML-mailbodies met niet-MIME-clean tekens weigert, die de agent af en toe produceert bij het transcriberen van dialect. We saneren bij binnenkomst.

def sanitise_for_exchange(html_body: str) -> str:
    cleaned = html_body.encode("ascii", "xmlcharrefreplace").decode("ascii")
    cleaned = cleaned.replace("\r\n", "\n").replace("\n", "<br/>")
    return f"<html><body>{cleaned}</body></html>"

Saaie code die voorkomt dat de hele ingest-pipeline om 02:00 stilvalt omdat een beller uit West-Brabant iets zegt dat het speech-to-text-model leest als een control character.

De andere Exchange-valkuil is throttling. Schrijf vanuit één verbinding op volle snelheid en de server laat nieuwe requests vallen na ongeveer 60 berichten in 90 seconden, met een misleidende 'too many concurrent connections'-fout; de echte limiet is throughput per service-account per source-IP. We rouleren tussen twee service-accounts en accepteren 200ms backoff per bericht. Trager dan we willen. Betrouwbaar. Het condoleance-archief heeft geen realtime write-latency nodig; het heeft nodig dat elk bericht precies één keer in de juiste map landt, en dat is wat we hebben.

Toon, taal en de dingen die een voice agent niet mag zeggen

De helft van het werk zat in prompt en toon. Drie regels waar we op landden, en eentje die we bewust niet opschreven.

De agent gebruikt het woord 'spijt' niet. Nederlandse families vertelden ons, interview na interview, dat 'het spijt me' uit een stem waarvan ze vermoeden dat hij geautomatiseerd is, voelt als gespeeld. We gebruiken 'ik begrijp dat dit een zwaar moment is' of, vaak beter, een langere pauze voor de volgende vraag.

De agent herhaalt de doodsoorzaak nooit terug. Zegt de beller dat zijn vader is omgekomen bij een auto-ongeluk, dan zegt de agent niet 'uw vader is overleden bij een auto-ongeluk'. Hij zegt 'dank u, dat heb ik genoteerd'. Een traumatisch detail herhalen met synthetische prosodie is de snelste manier om vertrouwen te verliezen.

De agent biedt altijd, binnen de eerste dertig seconden, de optie aan om een mens te spreken. Ook als de beller rustig is. Ook als de agent het goed doet. Het aanbod zelf is het veiligheidsventiel.

De regel die we overwogen en niet codificeerden: hoe om te gaan met de lange stilte na een moeilijke vraag. Vroeg in de bouw hadden we een versie live die na vier seconden het gat dichtte met 'ik luister naar u'. Twee bellers klaagden, los van elkaar, dat het voelde als een chatbot die checkte of er nog iemand was. We hebben het eruit gehaald. De agent wacht nu tot twaalf seconden in stilte voordat hij zachtjes vraagt of de beller er nog is. Sommige momenten zijn niet om op te vullen.

Wat we hebben gemeten na 90 dagen

Cijfers uit het eerste kwartaal in productie, uit de interne evaluatie van de onderneming.

De opnamerate buiten kantooruren ging van 71% naar 99,4%. De resterende 0,6% zit voornamelijk in carrier-problemen aan de inbound kant.

De mediane tijd tot een mens, voor gesprekken die voor escalatie zijn gemarkeerd, is 28 seconden. Het 45-secondenbudget zit ruim.

Het team uitvaartverzorgers krijgt 38% minder cold-start nachtgesprekken, omdat de agent de basics (naam van de overledene, locatie, of een schouwarts gebeld is) al heeft verzameld en in het dossier heeft geplakt voordat de mens opneemt.

De gemiddelde gesprekstijd vóór overdracht daalde van 4:12 naar 1:38 op het happy path, omdat de agent stopt met vragen zodra het dossier heeft wat het nodig heeft. Dossiervolledigheid bij de eerste opslag, gemeten tegen de post-uitvaart-audit van de intake-lead van de onderneming, zit op 94%. De resterende 6% is bijna altijd een ontbrekend overlijdensadres of een verkeerd verstane achternaam; beide vallen veilig in de ochtendreview-queue van het personeel, niet in de ervaring van de familie.

De false-positive-escalatierate zit op 4,1%. De gestelde tolerantie van het team was 8%. Ze hebben ons gevraagd hem niet verder aan te draaien.

Nul false negatives in de categorie suïcide-context, over ongeveer 12.200 geclassificeerde gesprekken. We geloven niet dat dit cijfer voor altijd blijft staan. We hebben elk kwartaal een externe review van gemarkeerd-versus-werkelijk staan, uitgevoerd door een clinicus die geen deel uitmaakt van het bouwteam.

Het kleinste wat je vandaag kunt doen

Beheer je een telefoonlijn buiten kantooruren die rouw, crisis of medische urgentie raakt, doe dan dit voordat je iets anders doet: schrijf de lijst op van conversationele signalen die betekenen 'stop de agent, haal een mens, nu'. Niet het happy path. De afslag. Kun je die signalen niet in gewone taal benoemen, dan kun je geen voice agent bouwen die ze afhandelt, en moet je er ook geen kopen.

Toen we dit bouwden voor de Tilburgse uitvaartonderneming, zat het zwaarste werk niet in de speech-stack of het EWS-loodgieterswerk. Het zat in een kamer zitten met de uitvaartverzorgers en regel voor regel, in het Nederlands, de escalatielijst opschrijven, totdat iedereen in de kamer het eens was. Dat gesprek, meer dan de code, is wat de voice agent veilig maakte om aan de lijn te zetten.

Kern

Bouw de afslag voor het happy path. Kun je de signalen niet benoemen die de agent zouden moeten stoppen om een mens te bellen, dan kun je hem ook niet shippen.

FAQ

Hoe snel escaleert de agent een gemarkeerd gesprek naar een mens?

Binnen 45 seconden end-to-end, inclusief doorbellen via het oproeprooster. De mediaan in productie is 28 seconden, over ongeveer 12.200 geclassificeerde gesprekken.

Bespreekt de voice agent ooit suïcide direct met bellers?

Nee. Worden er signalen gedetecteerd, dan stopt hij direct met triage en draagt het gesprek over aan een mens, met de relevante context in het dossier geschreven. De agent is geen crisishulpverlener en is niet gebouwd om als zodanig te functioneren.

Waarom Exchange 2016 behouden in plaats van migreren naar Microsoft 365?

De hoofd-engineer van de uitvaartonderneming vroeg ons dat niet te doen. Elf jaar institutionele kennis leeft in die public folder-structuur. De voice agent schrijft via EWS naar dezelfde mappen die het personeel al vertrouwt.

Hoe ga je om met het BSN-veld uit Uitvaartsoftware.nl?

De agent leest nooit een BSN voor en vraagt de beller ook niet om er een te bevestigen. De capture gebeurt via een aparte beveiligde one-time-link die na het gesprek per SMS wordt verzonden, afgehandeld buiten het transcriptpad van de agent.

voice agentsai agentsintegrationsarchitectureoperationsworkflow

Iets bouwen?

Start een project