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 |
|---|---|---|
|
| Content SID van het goedgekeurde alert-template |
|
| Content SID van het CLAIM-update-template. Leeg → freeform fallback |
|
| Content SID van het RESOLVE-update-template. Leeg → freeform fallback |
|
| WhatsApp Business-afzendernummer (Twilio) |
| bv. | Basisadres voor de deep link in |
Secrets (via wrangler secret put)
Secret | Omschrijving |
|---|---|
| Twilio account SID; gebruikt als gebruikersnaam bij de Twilio REST API |
| 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 |
|---|---|
| Naam van de service/module die een storing heeft |
| Deep link naar de publieke alertpagina: |
Templatetekst
De tekst van het template leeft in Twilio (niet in de repository). De inhoud is:
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
Respondersop 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 echtAlertEventbeschikbaar.
Core-helper
Beide routes maken gebruik van sendWhatsApp() in src/lib/server/twilio.ts:
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 |
| Zet de meest recente |
RESOLVE |
| Zet de meest recente |
STATUS |
| 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
TwiML-antwoord
De webhook antwoordt altijd met een TwiML <Message>-element:
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()insrc/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 metopgelost.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/resolveform actions insrc/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
AlertEventviagetAlertEventByIdinsrc/lib/server/alert-events.ts; als het record niet bestaat, retourneert de server een404.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 |
|---|---|
|
|
|
|
| Inbound webhook: validatie, responder-lookup, commandoverwerking, TwiML-antwoord, broadcast |
|
|
| Admin test-send pagina |
| Publieke alertpagina — serverkant laden + 404-afhandeling |
| Publieke alertpagina — weergave |
|
|
|
|