AutoMate Technisch Help

StatusPage — WhatsApp-integratie

De StatusPage Worker stuurt on-call medewerkers een WhatsApp-bericht wanneer een AutoMate-module een storing meldt. Medewerkers kunnen vervolgens de AlertEvent-lifecycle sturen door te reageren met commandowoorden. De integratie verloopt volledig via Twilio: outbound via een goedgekeurd WhatsApp Business-template, inbound via een webhook die door Twilio-signatuurvalidatie wordt beveiligd.

Configuratie

Environment variables (wrangler.jsonc)

Variabele

Waarde

Omschrijving

TWILIO_TEMPLATE_SID

HX2c2c14bc4b94b375bc99d830e82c39cb

Content SID van het goedgekeurde alert-template

TWILIO_CLAIM_TEMPLATE_SID

HXe4c8c77c9a0910f50d0f0ee5d5d4ca02

Content SID van het CLAIM-update-template. Leeg → freeform fallback

TWILIO_RESOLVE_TEMPLATE_SID

HXa7ef6b4bfdd31ec3458873d7958d7e06

Content SID van het RESOLVE-update-template. Leeg → freeform fallback

TWILIO_FROM_NUMBER

+3197010288365

WhatsApp Business-afzendernummer (Twilio)

PUBLIC_BASE_URL

bv. https://status.automate365.cloud

Basisadres voor de deep link in {{2}}

Secrets (via wrangler secret put)

Secret

Omschrijving

TWILIO_ACCOUNT_SID

Twilio account SID; gebruikt als gebruikersnaam bij de Twilio REST API

TWILIO_AUTH_TOKEN

Twilio auth token; wordt ook gebruikt om de HMAC-SHA1-handtekening van inkomende webhooks te valideren

Template-variabelen

Het template heeft twee variabelen:

Variabele

Gevuld met

{{1}}

Naam van de service/module die een storing heeft

{{2}}

Deep link naar de publieke alertpagina: PUBLIC_BASE_URL/alert/<id>

Templatetekst

De tekst van het template leeft in Twilio (niet in de repository). De inhoud is:

Service {{1}} heeft een storing. Onderzoek dit en klik op de link hieronder om naar het statusportaal te gaan: {{2}} Reageer met: • CLAIM — om de melding op te pakken • RESOLVE — zodra opgelost • STATUS — voor de huidige stand

Outbound: alert verzenden

Een alertbericht wordt vanuit twee plaatsen verzonden.

1. Alert trigger (productie)

src/routes/admin/alert-events/+page.server.ts — de trigger form action:

  • Haalt alle actieve Responders op uit de D1-database (IsActive = 1).

  • Roept voor elke responder sendWhatsApp() aan met {{1}} = modulenaam en {{2}} = PUBLIC_BASE_URL/alert/<alertEventId>.

2. Admin test-send pagina

src/routes/admin/whatsapp/+page.server.ts — de test-pagina voor admins:

  • Stuurt hetzelfde template naar een door de admin opgegeven nummer.

  • {{2}} wijst in dit geval naar de homepagina (PUBLIC_BASE_URL), want er is geen echt AlertEvent beschikbaar.

Core-helper

Beide routes maken gebruik van sendWhatsApp() in src/lib/server/twilio.ts:

// src/lib/server/twilio.ts export async function sendWhatsApp( env: Env, to: string, message: string, options: { contentSid?: string; contentVariables?: Record<string, string> } = {} ): Promise<{ ok: boolean; sid?: string; error?: string }> { // ... bouwt een x-www-form-urlencoded body en POST't naar de Twilio REST API met fetch(). // Met options.contentSid → ContentSid + ContentVariables (template). // Zonder → freeform Body (message). }

Een aanroep met options.contentSid verstuurt het goedgekeurde template; een aanroep met alleen message (geen options) verstuurt een freeform-bericht. Freeform werkt alleen binnen een actief 24-uurs sessievenster.

Inbound: commando's van responders

Wanneer een responder reageert op een WhatsApp-bericht, stuurt Twilio een POST-request naar src/routes/api/webhooks/twilio/+server.ts.

Handtekeningvalidatie

Elk inkomend verzoek wordt geverifieerd met een HMAC-SHA1-handtekening (via validateTwilioSignature() in src/lib/server/twilio.ts). De handtekening wordt berekend over de volledige webhook-URL plus de gesorteerde POST-parameters en ondertekend met TWILIO_AUTH_TOKEN. Een ongeldig verzoek retourneert 401.

Responder-lookup

De webhook haalt de From-parameter op (het WhatsApp-nummer van de afzender) en zoekt een overeenkomende rij op in de Responders-tabel met IsActive = 1. Onbekende nummers worden gelogd voor misbruikdetectie maar verder genegeerd — er volgt geen foutantwoord naar Twilio.

Commando's en aliases

Commandowoord

Aliases

Actie

CLAIM

ja, mine, ik

Zet de meest recente Triggered-alert (met ActiveLock) op Claimed voor deze responder

RESOLVE

opgelost, done, klaar

Zet de meest recente Claimed-alert van deze responder op Resolved, wist ActiveLock

STATUS

stand, info

Rapporteert de huidige stand van de meest recente alert; geen statuswijziging

(onbekend)

Retourneert een help-reply met uitleg over de drie beschikbare commando's

Commando's zijn case-insensitief en worden vergeleken op getrimde bodytekst (exact match).

Statusovergangen

alert aangemaakt

CLAIM (responder neemt melding op)

RESOLVE (responder meldt opgelost)

STATUS (geen wijziging)

STATUS (geen wijziging)

Triggered

Claimed

Resolved

TwiML-antwoord

De webhook antwoordt altijd met een TwiML <Message>-element:

<?xml version="1.0" encoding="UTF-8"?> <Response> <Message>Melding #42 (Identity) is geclaimd. ✓</Message> </Response>

Omdat de responder zich binnen het 24-uurs sessievenster bevindt (hij reageert immers op een eerder ontvangen bericht), is dit een geldig freeform TwiML-antwoord en wordt het direct afgeleverd — zonder het goedgekeurde Business-template.

Antwoorden per commando:

  • CLAIM — bevestiging met alert-id en modulenaam.

  • RESOLVE — bevestiging met alert-id en modulenaam.

  • STATUS — samenvatting: huidige status, modulenaam, naam van de claimer (indien aanwezig) en de deep link naar de publieke alertpagina. De naam van de claimer is zichtbaar voor de responder die STATUS opvraagt; dit is bewust toegestaan omdat de ontvanger een geverifieerde responder is.

  • Onbekend commando — een help-reply met uitleg over CLAIM, RESOLVE en STATUS.

Wide-spread update (broadcast)

Wanneer een melding wordt geclaimd of opgelost — via WhatsApp óf via de admin-portal — krijgen alle andere actieve responders automatisch een WhatsApp-update, zodat de hele on-call groep op de hoogte blijft.

  • Helper: broadcastAlertUpdate() in src/lib/server/notifications.ts.

  • Afleverkanaal: er zijn twee goedgekeurde update-templates — één voor CLAIM (TWILIO_CLAIM_TEMPLATE_SID), één voor RESOLVE (TWILIO_RESOLVE_TEMPLATE_SID). De helper kiest op basis van de actie. Een template levert altijd af, ook buiten het 24u-venster. Is de betreffende SID leeg, dan valt de helper terug op een freeform-bericht.

  • Waarom twee templates: Meta keurt één template met een werkwoord-variabele af ("too broad" / generieke content). De actie staat daarom in de vaste tekst van elk template; de variabelen zijn pure data.

  • Template-variabelen (volgorde moet exact matchen, beide templates): {{1}}=modulenaam, {{2}}=naam actor, {{3}}=deep link. De modulenaam identificeert de melding (één openstaande melding per module), dus een numeriek melding-id is niet nodig.

  • Meta-goedkeuringsregels (Utility): de tekst mag niet met een variabele beginnen of eindigen en moet voldoende vaste context bevatten — anders wordt het template afgewezen en blijft "WhatsApp business initiated" rood (precies het kanaal dat de broadcast nodig heeft). Voorgestelde, goedkeuringsvriendelijke tekst — CLAIM: Hallo, er is een update voor de AutoMate-storingsmelding voor {{1}}: de melding is opgepakt door {{2}}. Bekijk de actuele status in het statusportaal via {{3}} — je ontvangt dit bericht als on-call medewerker.; RESOLVE: idem met opgelost.

  • De actor wordt uitgesloten: bij een WhatsApp-commando via excludeResponderId (de afzender), bij de admin-portal is de actor een admin en geen responder, dus krijgen alle responders de update.

  • Aanroeppunten: de inbound webhook (na CLAIM en na RESOLVE) en de claim/resolve form actions in src/routes/admin/alert-events/+page.server.ts.

  • De broadcast is best-effort en wordt in een .catch() afgevangen: een fout mag het TwiML-antwoord of de admin-actie nooit blokkeren.

Publieke alertpagina

De deep link in {{2}} wijst naar de route src/routes/(public)/alert/[id]/.

  • De pagina laadt het AlertEvent via getAlertEventById in src/lib/server/alert-events.ts; als het record niet bestaat, retourneert de server een 404.

  • Getoond op de pagina: modulenaam, severity-badge, huidige status, tijdstempels (gemeld / geclaimd / opgelost) en het alertbericht.

  • Privacy: de naam van de claimer (responder) wordt bewust niet getoond. De pagina is publiek en unauthenticated. Namen van medewerkers verschijnen alleen in het WhatsApp STATUS-antwoord, gericht aan een geverifieerde responder.

Onderdrukking van alerts

Nieuwe AlertEvent-notificaties worden onderdrukt wanneer:

  • Er een actief maintenance window is, of

  • Er een open portaalincident bestaat dat de betreffende module dekt.

De onderdrukking wordt gecontroleerd in de trigger-action voordat sendWhatsApp() wordt aangeroepen.

Bestandsoverzicht

Bestand

Omschrijving

src/lib/server/twilio.ts

sendWhatsApp() + validateTwilioSignature()

src/lib/server/notifications.ts

broadcastAlertUpdate() — wide-spread CLAIM/RESOLVE-update naar overige responders

src/routes/api/webhooks/twilio/+server.ts

Inbound webhook: validatie, responder-lookup, commandoverwerking, TwiML-antwoord, broadcast

src/routes/admin/alert-events/+page.server.ts

trigger form action — stuurt WhatsApp naar alle actieve responders

src/routes/admin/whatsapp/+page.server.ts

Admin test-send pagina

src/routes/(public)/alert/[id]/+page.server.ts

Publieke alertpagina — serverkant laden + 404-afhandeling

src/routes/(public)/alert/[id]/+page.svelte

Publieke alertpagina — weergave

src/lib/server/alert-events.ts

getAlertEventById + lifecycle-mutaties (TriggeredClaimedResolved)

wrangler.jsonc

TWILIO_TEMPLATE_SID, TWILIO_FROM_NUMBER, PUBLIC_BASE_URL

Last modified: 30 June 2026