Koppel events (UUIDs, triggers, payloads)
Feiten over de events die de Koppel-feature aanmaakt via eventService. Bedoeld voor beheerders die per event-type een mailtemplate en recipients willen configureren in de admin UI.
Bron-definities: apps/api/src/api/koppel/koppelEventTypes.ts. Alle dispatch-sites zitten in pluginLicenseService.ts.
Auto-registratie
eventService.create() registreert een event-type automatisch bij de eerste fire (UUID is leidend). Er is geen seed of migratie nodig. Na deploy kan een beheerder direct een notificatie (mailtemplate, recipients) koppelen aan elk van onderstaande event-types.
Events per flow
| # | Flow | Event-naam | UUID | Dispatch |
|---|---|---|---|---|
| 1 | Landing | Koppel registratie aangevraagd | e7b2f1a4-7c1b-4ba4-9e0a-9c1d5a8f2a10 |
pluginLicenseService.ts:172 |
| 2 | Landing | Koppel registratie duplicaat | 2f4c9a6b-0e9d-4a7c-bf7c-6d1e3a9f0b22 |
pluginLicenseService.ts:136 |
| 3 | Plugin | Koppel plugin eerste activatie | 5a3e8d72-1b4f-4c9e-8a07-2d6f9b1c4e83 |
pluginLicenseService.ts:329 |
| 4 | Plugin | Koppel plugin extra activatie | 8b1c4f2a-9d6e-4732-a015-7e8b3c5d2f49 |
pluginLicenseService.ts:347 |
| 5 | Plugin | Koppel licentie nadert maximum activaties | a2e8f4b7-5c91-4d36-b08e-9f2a6d1c3b75 |
pluginLicenseService.ts:368 |
| 6 | Plugin / admin | Koppel licentie verlopen | c4f9e6d1-3a82-4b07-9e5a-1d8c2f6b9a04 |
pluginLicenseService.ts:235 (via markLicenseExpired) |
| 7 | Admin | Koppel licentie ingetrokken | d7a3b9c5-6e02-4f81-a4d6-3b8c1e9f7d62 |
pluginLicenseService.ts:513 |
| 8 | Job (dagelijks) | Koppel activatie gedeactiveerd wegens inactiviteit | 4d508036-7d1a-4aac-a1f9-8e46cae37253 |
pluginLicenseService.ts (via deactivateInactiveActivations) |
1. Koppel registratie aangevraagd
Trigger: eerste aanvraag op een nieuw e-maildomein via het Koppel-landingsformulier (POST /public/koppel/register). Een activatiecode wordt gemaild.
| Veld | Type | Bron |
|---|---|---|
email |
string | input.email (formulier) |
name |
string | input.name (formulier) |
organization |
string | input.organization (formulier) |
activationKey |
string (UUID) | net-gegenereerd via randomUUID() |
expiresAt |
string (ISO-8601) | now + TRIAL_DURATION_DAYS |
2. Koppel registratie duplicaat
Trigger: tweede aanvraag op een e-maildomein dat al een pending of active Koppel-licentie heeft. Er wordt géén nieuwe activatiecode verstuurd; auto-enrollment geldt vanaf de tweede activatie.
| Veld | Type | Bron |
|---|---|---|
email |
string | input.email (nieuwe aanvrager) |
name |
string | input.name (nieuwe aanvrager) |
organization |
string | input.organization (nieuwe aanvrager) |
domain |
string | lowercased deel na @ van input.email |
existingLicenseId |
string | id van de al bestaande licentie |
primaryApplicantEmail |
string | existing.email (originele aanvrager) |
primaryApplicantName |
string | existing.name (originele aanvrager) |
3. Koppel plugin eerste activatie
Trigger: een activatiecode wordt voor het eerst gebruikt via POST /public/koppel-plugin/activate. Status gaat van pending naar active, gekoppeld aan een Microsoft- en Exact Online-tenant.
| Veld | Type | Bron |
|---|---|---|
name |
string | license.name (uit registratie) |
email |
string | input.email (daadwerkelijke activeerder uit de plugin) |
applicantEmail |
string | license.email (kan in zeldzame gevallen afwijken van email) |
organization |
string | license.organization |
activationKey |
string | license.activationKey |
microsoftTenantId |
string | input.microsoftTenantId (binding gezet bij deze activatie) |
exactOnlineTenantId |
string | input.exactOnlineTenantId |
4. Koppel plugin extra activatie
Trigger: een extra gebruiker activeert de plugin binnen een al actieve organisatie-licentie (auto-enrollment).
| Veld | Type | Bron |
|---|---|---|
email |
string | input.email (auto-enrollment-gebruiker) |
organization |
string | license.organization |
activationKey |
string | license.activationKey |
microsoftTenantId |
string | input.microsoftTenantId (gevalideerd tegen license) |
exactOnlineTenantId |
string | input.exactOnlineTenantId (gevalideerd tegen license) |
activationsUsed |
number | nieuwe count, inclusief deze activatie |
Geen name-veld: bij auto-enrollment is alleen het e-mailadres van de extra gebruiker bekend.
5. Koppel licentie nadert maximum activaties
Trigger: de active count passeert eenmalig de drempel NEAR_MAX_ACTIVATIONS_THRESHOLD (80 van 100). Trial-licenties tellen niet mee. De strikte ongelijkheid voorkomt herhaling bij elke vervolg-activatie boven de drempel.
| Veld | Type | Bron |
|---|---|---|
licenseId |
string | license.id |
name |
string | license.name (aanvrager) |
email |
string | license.email (aanvrager, niet de activeerder) |
organization |
string | license.organization |
activationKey |
string | license.activationKey |
activationsUsed |
number | nieuwe count die de drempel passeert |
threshold |
number | constante NEAR_MAX_ACTIVATIONS_THRESHOLD (80) |
max |
number | constante MAX_ACTIVATIONS (100) |
6. Koppel licentie verlopen
Trigger: een licentie wordt als verlopen gemarkeerd. Gebeurt:
- Automatisch bij
activate,validateofcheckAccessvoorbijexpiresAt. - Handmatig door een beheerder via
PUT /admin/koppel/licenses/:idmetstatus: 'expired'.
Beide paden hergebruiken de helper markLicenseExpired op regel 232, dus de payload is identiek.
| Veld | Type | Bron |
|---|---|---|
licenseId |
string | license.id |
name |
string | license.name |
email |
string | license.email |
organization |
string | license.organization |
activationKey |
string | license.activationKey |
expiresAt |
string (ISO-8601) | license.expiresAt.toISOString() |
activationsUsed |
number | count op moment van verlopen |
7. Koppel licentie ingetrokken
Trigger: een beheerder zet de status van een licentie op revoked via PUT /admin/koppel/licenses/:id. Een no-op patch (status ongewijzigd) triggert geen event.
| Veld | Type | Bron |
|---|---|---|
licenseId |
string | existing.id |
name |
string | existing.name |
email |
string | existing.email |
organization |
string | existing.organization |
activationKey |
string | existing.activationKey |
previousStatus |
string | status voor de update (pending, active of expired) |
activationsUsed |
number | count op moment van revoke |
8. Koppel activatie gedeactiveerd wegens inactiviteit
Trigger: de dagelijkse job DEACTIVATE_INACTIVE_KOPPEL_ACTIVATIONS (02:15) zet een activatie op inactief (deactivatedAt) zodra die langer dan INACTIVITY_PERIOD_MONTHS (3) niet gebruikt is, mits de licentie active is (geen trial/expired/revoked). Soft-deactivatie is omkeerbaar: hernieuwd gebruik (touchLastActiveAt) wist deactivatedAt. De notificatie is bedoeld voor de organisatie-beheerder, zodat die de medewerker kan laten heractiveren.
| Veld | Type | Bron |
|---|---|---|
licenseId |
string | license.id |
organization |
string | license.organization |
orgAdminName |
string | license.name (de licentie-aanvrager/eerste gebruiker) |
orgAdminEmail |
string | license.email (bedoelde ontvanger van de melding) |
deactivatedEmail |
string | activation.email (de medewerker wiens activatie is gedeactiveerd) |
activationKey |
string | license.activationKey |
lastActiveAt |
string (ISO-8601) | laatste gebruik vóór deactivatie |
deactivatedAt |
string (ISO-8601) | moment van deactivatie |
Dispatch-mechanisme
Alle koppel-events lopen door één privé-helper: pluginLicenseService.dispatchKoppelEvent (regel 198).
| Aspect | Gedrag |
|---|---|
| Aanroep | eventService.create(eventType, null, null, [], { data }) (geen user, geen source-doc, geen targets) |
| Foutbeleid | Niet-blokkerend. Errors worden gelogd via loggerService.error, niet doorgegooid. Een falende notificatie blokkeert nooit de onderliggende actie (registratie of activatie). |
| Tenant-context | Publieke Koppel-endpoints draaien zonder tenant-middleware. De helper laadt de Koppel-tenant uit env.KOPPEL_TENANT_UUID en opent een AsyncLocalStorage-scope, zodat de event- en notification-pipeline op de juiste admin-connectie schrijven. |
| Metric | koppelEventCounter (tapster_koppel_event_total) wordt per fire opgehoogd met label event: <name>. Zie apps/api/src/monitoring/koppel-metrics.ts. |
Event-initiator (User)
Sinds 2026-05 heeft elk koppel-event een User-initiator (event.user). Bij KOPPEL_REGISTRATION_REQUESTED en KOPPEL_REGISTRATION_DUPLICATE is dit de aanvrager (een NOT_ACTIVATED user, automatisch aangemaakt in de KOPPEL_TENANT). Bij KOPPEL_PLUGIN_ACTIVATED_FIRST en _ADDITIONAL is dit de geactiveerde plugin-gebruiker. Bij KOPPEL_LICENSE_EXPIRED, KOPPEL_LICENSE_NEAR_MAX_ACTIVATIONS en KOPPEL_LICENSE_REVOKED is dit de license-eigenaar.
Notificaties naar de initiator
Configureer een NotificationType met recipient-role 'OWNER' om de initiator (event.user) te bereiken. Let op: de 'SOURCE'-role is herkend maar niet geïmplementeerd in getRecipientsHelper.ts, dus gebruik altijd 'OWNER'.
Levenscyclus van koppel-users
- Aangemaakt bij registratie of activatie (state
NOT_ACTIVATED, roledefault). - Bij license-revoke of activation-delete:
statewordtTO_BE_REMOVED, behalve als de user nog andere actieve activations heeft. Na 14 dagen anonimiseert de bestaandeanonimiseer-gebruikers-job ze permanent. - Bij license-expired: een dagelijkse job (
MARK_EXPIRED_KOPPEL_USERS) markeert users als TO_BE_REMOVED zodraexpiresAt + 30 dagen graceverstreken is. Daarna volgt de bestaande 14-dagen anonimisering, dus totaal 44 dagen tussen expiry en permanent verwijderen.
Configuratie en gedrag
| Aspect | Status |
|---|---|
| Feature flags op event creation | Geen. Alle 8 events vuren altijd zodra de trigger geldt. |
| Per-tenant toggles | Geen. Koppel-events horen bij de Koppel-tenant. |
| Per event-type configureerbaar | Ja, via admin UI: mailtemplate (UUID), recipients (rollen), digest-flag. |
| Idempotency | Notificaties worden gededupliceerd via idempotency-key in taskService. |
| Rate-limit | Notificaties: 120/minuut per tenant. Event-creation zelf is niet gethrottled. |
Constanten
Gedefinieerd in apps/api/src/api/koppel/pluginLicenseModel.ts:
| Constante | Waarde | Gebruikt door |
|---|---|---|
TRIAL_DURATION_DAYS |
14 |
expiresAt bij events 1 en 6 |
MAX_ACTIVATIONS |
100 |
max in event 5 |
NEAR_MAX_ACTIVATIONS_THRESHOLD |
80 |
threshold in event 5 |
INACTIVITY_PERIOD_MONTHS |
3 |
inactiviteitsgrens voor event 8 (in pluginLicenseActivationRepository.ts) |
Velden die in templates beschikbaar zijn
Let op bij het schrijven van mailtemplates:
activationKeyzit in alle 7 events.licenseIdzit in events 3 niet, en in 1, 2, 4 niet (alleen 5, 6, 7).namezit in events 4 (auto-enrollment) niet.emailzit in alle 7 events, maar de betekenis verschilt:- Events 1, 2, 5, 6, 7:
emailis de aanvrager. - Event 3:
emailis de activeerder;applicantEmailis de aanvrager. - Event 4:
emailis de auto-enrollment-gebruiker, er is geen aanvrager-veld.
- Events 1, 2, 5, 6, 7:
Bron-bestanden
- Event-type definities:
apps/api/src/api/koppel/koppelEventTypes.ts - Dispatch en triggers:
apps/api/src/api/koppel/pluginLicenseService.ts - Routers:
apps/api/src/api/koppel/{koppelPublicRouter,koppelPluginRouter,koppelAdminRouter}.ts - EventService:
apps/api/src/api/event/eventService.ts - Metric:
apps/api/src/monitoring/koppel-metrics.ts - Constanten:
apps/api/src/api/koppel/pluginLicenseModel.ts