Ga naar hoofdinhoud

PRD: Automatische Zaptec Laadsessie-Integratie

Status: Draft Auteur: Product & Engineering Datum: 2026-03-19 Versie: 1.0


1. Context & Waarom Nu

Markt- en Concurrentiecontext

  • Concurrenten (bijv. Lekkerladen.com) bieden dit al aan: Zaptec-gebruikers kunnen hun laadpaal koppelen en zien de volgende dag al hun laadsessies en potentiële ERE-opbrengsten. Dit is een directe concurrentiedreiging voor ons platform.
  • Huidige situatie: Onze gebruikers moeten handmatig CSV/Excel-bestanden downloaden uit het Zaptec-portaal en uploaden naar ons platform. Dit is foutgevoelig, tijdrovend en een significante drempel voor adoptie.
  • Zaptec is ons premium product: Den Hartog verkoopt de Zaptec Go 2 als MID-gecertificeerde oplossing. Het is onlogisch dat juist Zaptec-gebruikers de meest omslachtige data-invoer hebben.

Waarom Nu

  • Concurrentiedruk: Lekkerladen.com heeft automatische Zaptec-koppeling al live. Elke week vertraging is een week waarin potentiële klanten naar de concurrent gaan.
  • Zaptec API is beschikbaar: Zaptec biedt een volledige REST API (api.zaptec.com) met OAuth 2.0 authenticatie en een dedicated charge history endpoint. De technische basis is er.
  • Bestaande import-infrastructuur: Ons platform heeft al ChargingSession, MonthlyKwhRecord, en KwhImportJob modellen. De Zaptec API-data kan direct in deze bestaande structuur worden opgeslagen.
  • Gebruikersretentie: Automatische data-import verhoogt engagement — gebruikers zien dagelijks hun voortgang zonder actie te hoeven ondernemen.

Zaptec API Overzicht

De Zaptec Cloud API biedt:

AspectDetails
Base URLhttps://api.zaptec.com/
AuthenticatieOAuth 2.0 Resource Owner Password Credentials (ROPC)
Token endpointPOST /oauth/token (grant_type=password, scope=openid)
Token geldigheid1 uur (3600 seconden)
Charge historyGET /api/chargehistory — voltooide sessies met filters
ChargersGET /api/chargers — lijst van toegankelijke laders
InstallatiesGET /api/installation — lijst van installaties
Rate limits10 req/s sustained, 15 burst; token endpoint 1 req/s/IP
PaginatieMax 100 records per pagina
Push-optieAzure Service Bus (AMQP) voor near-realtime events
API versiev1.0 (stable, januari 2025)

Charge History Endpoint — GET /api/chargehistory:

  • Filters: InstallationId, UserId, ChargerId, From, To, GroupBy, DetailLevel
  • Paginatie: default 50 items, max 100 per pagina
  • Retourneert voltooide laadsessies met kWh, start/eindtijd, charger ID

2. Gebruikers & Jobs-to-Be-Done

Persona 1: Particuliere Zaptec-gebruiker

Beschrijving: Thuislader met een Zaptec laadpaal (Go, Go 2, of Pro) die deelneemt aan het ERE-programma.

JTBD: "Wanneer ik mijn Zaptec-account koppel aan het ERE-portaal, wil ik dat mijn laadsessies automatisch worden opgehaald, zodat ik zonder handmatig werk mijn ERE-opbrengsten kan volgen."

Huidige situatie: Moet maandelijks inloggen op portal.zaptec.com, een export downloaden, en dit bestand uploaden in het ERE-portaal. Veel gebruikers vergeten dit of vinden het te omslachtig.

Persona 2: Zakelijke Zaptec-gebruiker (BEDRIJF)

Beschrijving: Bedrijf met meerdere Zaptec-laadpunten onder één of meer installaties.

JTBD: "Wanneer ik mijn bedrijfs-Zaptec-account koppel, wil ik dat alle laadpunten automatisch worden gesynct, zodat mijn administratie voor het ERE-programma volledig geautomatiseerd is."

Huidige situatie: Moet per installatie of per laadpunt exporteren en uploaden — bij meerdere laadpunten een aanzienlijke administratieve last.

Persona 3: Admin Medewerker (Den Hartog)

Beschrijving: Verifieert kWh-records en verwerkt ERE-claims.

JTBD: "Wanneer laadsessies automatisch binnenkomen via de Zaptec API, wil ik erop kunnen vertrouwen dat de data betrouwbaar is en direct te koppelen aan het juiste laadpunt, zodat ik minder tijd kwijt ben aan handmatige verificatie."

Huidige situatie: Moet handmatig geüploade bestanden controleren op correctheid, ontbrekende periodes, en juiste serienummer-koppelingen.


3. Bedrijfsdoelen & Succesmetrieken

Leidende Indicatoren (zichtbaar binnen dagen/weken)

MetriekHuidige BaselineDoelTijdsbestek
Aantal Zaptec-koppelingen (connected accounts)0>30 actieve koppelingen6 weken na livegang
% Zaptec-gebruikers dat koppelt (vs. handmatig uploadt)0%>50% van actieve Zaptec-gebruikers8 weken na livegang
Gemiddelde tijd tussen laadsessie en beschikbaarheid in portaalDagen tot weken (handmatig)<24 uur (automatisch)Week 1 na livegang
Sync-foutpercentageN.v.t.<2% van sync-runsContinu

Achterblijvende Indicatoren (bedrijfsresultaten)

MetriekHuidige BaselineDoelTijdsbestek
Handmatige Zaptec-uploads per maand[Te meten]-80% (verplaatst naar automatisch)3 maanden
Admin-verwerkingstijd kWh-verificatie (Zaptec)[Te meten]-50% (betrouwbaardere data)3 maanden
Churn-rate Zaptec-gebruikers[Te meten]-20% (hogere engagement)6 maanden
Gemiddeld aantal dagen met ontbrekende sessiedata[Te meten]<1 dag achterstandContinu

4. Functionele Eisen

Phase 1: Koppeling & Dagelijkse Sync

FR-1: Zaptec-account koppeling
- Beschrijving: Gebruikers kunnen hun Zaptec-account koppelen aan het
ERE-portaal door hun Zaptec-inloggegevens in te voeren. Het systeem
valideert de credentials en slaat een versleutelde versie op.
- Acceptatiecriteria:
- [ ] Koppelpagina bereikbaar vanuit dashboard en profiel
- [ ] Gebruiker voert Zaptec e-mailadres en wachtwoord in
- [ ] Backend valideert credentials via POST /oauth/token op api.zaptec.com
- [ ] Bij succes: credentials worden versleuteld opgeslagen (AES-256-GCM)
- [ ] Bij fout: duidelijke foutmelding (ongeldige credentials, Zaptec onbereikbaar)
- [ ] Gebruiker ziet bevestiging met lijst van gevonden installaties en laders
- [ ] Werkt voor zowel PARTICULIER als BEDRIJF accounts
- [ ] BEDRIJF: koppeling geldt voor de organisatie, niet per gebruiker
- Prioriteit: P0
FR-2: Automatische laadpaal-matching
- Beschrijving: Na koppeling worden Zaptec-laders automatisch gematcht
aan geregistreerde laadpunten in het ERE-portaal op basis van
serienummer of charger ID.
- Acceptatiecriteria:
- [ ] Systeem haalt lijst van laders op via GET /api/chargers
- [ ] Automatische matching op serienummer (ChargingPoint.serialNumber ↔ Zaptec charger ID)
- [ ] Bij geen match: gebruiker kan handmatig koppelen (dropdown van eigen laadpunten)
- [ ] Bij meerdere installaties: alle installaties worden opgehaald
- [ ] Matched laders worden opgeslagen in een ZaptecChargerLink tabel
- [ ] Niet-gematchte laders worden getoond als suggestie ("Wil je deze laadpaal registreren?")
- Prioriteit: P0
FR-3: Dagelijkse sessie-sync (cron job)
- Beschrijving: Een dagelijkse cron job haalt nieuwe laadsessies op
voor alle gekoppelde Zaptec-accounts via GET /api/chargehistory.
- Acceptatiecriteria:
- [ ] Cron job draait dagelijks om 03:00 UTC (05:00 NL-tijd)
- [ ] Per gekoppeld account: token ophalen via /oauth/token
- [ ] Sessies ophalen sinds laatste succesvolle sync (From parameter)
- [ ] Paginatie: alle pagina's ophalen (max 100 per request)
- [ ] Sessies opslaan als ChargingSession met source = PLATFORM_ZAPTEC
- [ ] Deduplicatie op basis van Zaptec session ID (externalId)
- [ ] Rate limiting respecteren: max 10 req/s, exponential backoff bij 429
- [ ] Per account sequentieel verwerken, accounts onderling parallel (max 5 concurrent)
- [ ] Sync-resultaat loggen in ZaptecSyncLog (success/failure, aantal sessies, duur)
- [ ] Bij token-fout (401): account markeren als "credentials verlopen", gebruiker notificeren
- [ ] Bij Zaptec API-downtime: retry bij volgende sync-run, geen data verloren
- Prioriteit: P0
FR-4: Sessie-naar-maandrecord aggregatie
- Beschrijving: Geimporteerde sessies worden automatisch geaggregeerd
naar MonthlyKwhRecords, consistent met de bestaande import-flow.
- Acceptatiecriteria:
- [ ] Na succesvolle sync: aggregeer sessies per laadpunt per maand
- [ ] Hergebruik bestaande aggregatielogica uit kwhImportService
- [ ] Nieuwe sessies worden toegevoegd aan bestaande MonthlyKwhRecords (geen duplicaten)
- [ ] MonthlyKwhRecords krijgen status PENDING (bestaande verificatie-flow)
- [ ] Admin kan geautomatiseerde records herkennen (bron: "Zaptec API")
- Prioriteit: P0
FR-5: Koppelingsbeheer (dashboard)
- Beschrijving: Gebruikers kunnen hun Zaptec-koppeling beheren:
status bekijken, handmatig syncen, en ontkoppelen.
- Acceptatiecriteria:
- [ ] Dashboard-widget toont koppelstatus (gekoppeld/niet gekoppeld/fout)
- [ ] Laatste sync-tijdstip en resultaat zichtbaar
- [ ] "Nu synchroniseren" knop voor handmatige trigger (rate limited: max 1x per uur)
- [ ] "Ontkoppelen" knop met bevestigingsdialoog
- [ ] Bij ontkoppelen: credentials verwijderd, bestaande sessies blijven behouden
- [ ] Overzicht van gekoppelde laders met match-status
- [ ] Foutmeldingen bij sync-problemen (credentials verlopen, API onbereikbaar)
- Prioriteit: P0
FR-6: Credential-rotatie en foutafhandeling
- Beschrijving: Het systeem detecteert verlopen of gewijzigde
Zaptec-credentials en informeert de gebruiker.
- Acceptatiecriteria:
- [ ] Bij 401 response: koppelstatus naar "CREDENTIALS_EXPIRED"
- [ ] E-mailnotificatie naar gebruiker: "Je Zaptec-koppeling is verbroken"
- [ ] In-app notificatie op dashboard
- [ ] Gebruiker kan credentials updaten zonder volledig opnieuw te koppelen
- [ ] Na 3 opeenvolgende sync-failures: koppeling gepauzeerd (geen onnodige API-calls)
- [ ] Admin-dashboard toont overzicht van koppelingen met fouten
- Prioriteit: P1

Phase 2: Verbeterde Ervaring (Toekomst)

FR-7: Onboarding-integratie
- Beschrijving: Tijdens de onboarding-stap "Laadpaal" kan de gebruiker
direct zijn Zaptec-account koppelen in plaats van handmatig een model
te selecteren. Het systeem detecteert automatisch het laadpaalmodel.
- Acceptatiecriteria:
- [ ] Optie "Koppel je Zaptec-account" in de laadpaal-stap
- [ ] Na koppeling: laadpaalmodel automatisch ingevuld op basis van Zaptec charger data
- [ ] MID-certificeringsstatus automatisch bepaald op basis van model
- [ ] Serienummer automatisch ingevuld
- [ ] Gebruiker hoeft alleen locatie en foto te bevestigen
- Prioriteit: P2
FR-8: Historische data-import bij eerste koppeling
- Beschrijving: Bij het koppelen worden niet alleen toekomstige sessies
opgehaald, maar ook historische data (tot max 12 maanden terug).
- Acceptatiecriteria:
- [ ] Bij eerste koppeling: sessies ophalen vanaf max 12 maanden geleden
- [ ] Gebruiker kan startdatum kiezen
- [ ] Voortgangsindicator tijdens initiële import (kan enkele minuten duren)
- [ ] Historische sessies worden op dezelfde manier verwerkt als dagelijkse sync
- Prioriteit: P2
FR-9: Near-realtime sync via Service Bus (optioneel)
- Beschrijving: Voor gebruikers die near-realtime inzicht willen,
kan een Azure Service Bus (AMQP) subscription worden opgezet.
- Notities:
- Vereist activatie via Zaptec helpdesk per installatie
- Max 2 gelijktijdige subscriptions per installatie
- Subscriptions vervallen na 14 dagen inactiviteit
- Message TTL is 5 minuten — vereist always-on consumer
- Aanzienlijk hogere infrastructuurcomplexiteit
- Prioriteit: P3 (alleen evalueren als dagelijkse sync onvoldoende blijkt)

5. Niet-Functionele Eisen

Beveiliging (Kritisch)

  • Credential-opslag: Zaptec-wachtwoorden worden versleuteld opgeslagen met AES-256-GCM. De encryptiesleutel wordt beheerd via een omgevingsvariabele (ZAPTEC_CREDENTIAL_ENCRYPTION_KEY), niet in de codebase.
  • Token-caching: OAuth access tokens worden in-memory gecached (max 50 minuten, ruim onder de 60 minuten expiry) om onnodige token-requests te vermijden. Tokens worden nooit persistent opgeslagen.
  • Minimale rechten: Het systeem gebruikt alleen leesrechten (GET endpoints). Geen write/command operaties naar Zaptec laders.
  • Credential-verwijdering: Bij ontkoppeling worden credentials onmiddellijk en permanent verwijderd (niet soft-deleted).
  • Audit trail: Alle koppel/ontkoppel/sync-acties worden gelogd in de AuditLog.

Performance

  • Sync-doorlooptijd: Dagelijkse sync voor 100 accounts moet binnen 30 minuten voltooid zijn
  • Rate limiting: Respecteer Zaptec's 10 req/s limiet; implementeer token bucket met exponential backoff
  • Sequentiële verwerking per account: Voorkom dat meerdere sync-runs tegelijk dezelfde account verwerken (distributed lock of database flag)
  • Paginatie: Altijd alle pagina's ophalen; stop niet na de eerste pagina

Beschikbaarheid

  • Graceful degradation: Als de Zaptec API onbereikbaar is, heeft dit geen impact op de rest van het ERE-portaal
  • Retry-strategie: Gefaalde syncs worden automatisch opnieuw geprobeerd bij de volgende dagelijkse run
  • Circuit breaker: Na 5 opeenvolgende failures voor alle accounts: pauzeer sync-job, alert naar admin
  • Monitoring: Zaptec API-beschikbaarheid monitoren via https://zaptec.statuspage.io/

Privacy & AVG

  • Persoonsgegevens: Zaptec-credentials zijn persoonsgegevens — AVG-compliant verwerken
  • Dataminimalisatie: Alleen sessiedata ophalen die nodig is voor ERE-claims (kWh, tijdstip, charger ID)
  • Recht op verwijdering: Bij accountverwijdering worden ook Zaptec-credentials en sync-logs verwijderd
  • Verwerkersovereenkomst: Mogelijk nodig met Zaptec — juridisch te evalueren

Observability

  • Sync metrics (per run):
    • Aantal accounts gesynct (success/failure)
    • Totaal aantal sessies geimporteerd
    • Gemiddelde en p95 duur per account
    • Aantal 429 responses (rate limit hits)
    • Aantal credential-fouten
  • Alerting:
    • Sync-job niet gestart (cron failure)
    • 10% van accounts faalt

    • Sync duurt langer dan 60 minuten
  • Logging: Structured logging met syncRunId, accountId, installationId voor traceability

6. Scope

In Scope

  • Phase 1 (dit PRD, nu te bouwen):
    • Zaptec-account koppelen (credentials opslaan, valideren)
    • Automatische laadpaal-matching (charger ID ↔ ChargingPoint)
    • Dagelijkse cron job voor sessie-sync
    • Sessie-opslag als ChargingSession (source: PLATFORM_ZAPTEC)
    • Aggregatie naar MonthlyKwhRecord (hergebruik bestaande logica)
    • Dashboard-widget: koppelstatus, laatste sync, handmatige trigger
    • Ontkoppelen met credential-verwijdering
    • Credential-foutdetectie en gebruikersnotificatie
    • Admin-overzicht van alle Zaptec-koppelingen

Buiten Scope

  • Onboarding-integratie — Phase 2, na validatie van de basisflow
  • Historische import bij eerste koppeling — Phase 2
  • Service Bus (near-realtime) — Phase 3, alleen als dagelijkse sync onvoldoende is
  • Andere platforms (Alfen, Wallbox, etc.) — apart initiatief, maar architectuur moet uitbreidbaar zijn
  • Write-operaties naar Zaptec (remote start/stop, firmware) — niet nodig voor ERE
  • Zaptec webhook/callback — Zaptec biedt geen webhooks, alleen Service Bus
  • Mobiele Capacitor-specifieke flows — responsive web is voldoende

Toekomstige Overwegingen

  • Generieke "Platform Connector" architectuur voor meerdere laadpaalmerken
  • Zaptec Partner API (indien beschikbaar) voor directe klantautorisatie zonder wachtwoord
  • Automatische MID-certificeringsstatus ophalen uit Zaptec charger metadata
  • Dashboardwidget met real-time laadstatus (via Service Bus)
  • Integratie met de MID-metercontrole (PRD: prd-mid-meter-check-onboarding.md)

7. Technische Architectuur

Datamodel-uitbreiding

// Zaptec-account koppeling per gebruiker of organisatie
model ZaptecConnection {
id String @id @default(cuid())
userId String? @map("user_id")
organizationId String? @map("organization_id")

// Encrypted Zaptec credentials
encryptedUsername String @map("encrypted_username") @db.Text
encryptedPassword String @map("encrypted_password") @db.Text
encryptionIv String @map("encryption_iv") @db.VarChar(64)

// Connection state
status ZaptecConnectionStatus @default(ACTIVE)
consecutiveFailures Int @default(0) @map("consecutive_failures")
lastSyncAt DateTime? @map("last_sync_at")
lastSyncStatus String? @map("last_sync_status") @db.VarChar(50)
lastErrorMessage String? @map("last_error_message") @db.Text

// Metadata
zaptecUserId String? @map("zaptec_user_id") @db.VarChar(255)
installationCount Int @default(0) @map("installation_count")
chargerCount Int @default(0) @map("charger_count")

createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// Relations
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
chargerLinks ZaptecChargerLink[]
syncLogs ZaptecSyncLog[]

@@unique([userId])
@@unique([organizationId])
@@map("zaptec_connections")
}

// Koppeling tussen Zaptec charger en ERE ChargingPoint
model ZaptecChargerLink {
id String @id @default(cuid())
zaptecConnectionId String @map("zaptec_connection_id")
chargingPointId String? @map("charging_point_id")

// Zaptec charger identifiers
zaptecChargerId String @map("zaptec_charger_id") @db.VarChar(255)
zaptecChargerName String? @map("zaptec_charger_name") @db.VarChar(255)
zaptecInstallationId String @map("zaptec_installation_id") @db.VarChar(255)
zaptecSerialNumber String? @map("zaptec_serial_number") @db.VarChar(100)

// Match metadata
matchMethod String? @map("match_method") @db.VarChar(50) // "auto_serial", "manual"
lastSessionSync DateTime? @map("last_session_sync")

createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// Relations
zaptecConnection ZaptecConnection @relation(fields: [zaptecConnectionId], references: [id], onDelete: Cascade)
chargingPoint ChargingPoint? @relation(fields: [chargingPointId], references: [id], onDelete: SetNull)

@@unique([zaptecConnectionId, zaptecChargerId])
@@map("zaptec_charger_links")
}

// Sync-audit per run
model ZaptecSyncLog {
id String @id @default(cuid())
zaptecConnectionId String @map("zaptec_connection_id")

status String @db.VarChar(20) // SUCCESS, FAILURE, PARTIAL
sessionsImported Int @default(0) @map("sessions_imported")
sessionsDeduplicated Int @default(0) @map("sessions_deduplicated")
chargersProcessed Int @default(0) @map("chargers_processed")
durationMs Int? @map("duration_ms")
errorMessage String? @map("error_message") @db.Text

startedAt DateTime @default(now()) @map("started_at")
completedAt DateTime? @map("completed_at")

// Relation
zaptecConnection ZaptecConnection @relation(fields: [zaptecConnectionId], references: [id], onDelete: Cascade)

@@map("zaptec_sync_logs")
}

enum ZaptecConnectionStatus {
ACTIVE
CREDENTIALS_EXPIRED
PAUSED
DISCONNECTED
}

Nieuwe bestanden

BestandDoel
backend/src/services/zaptecApiClient.tsHTTP client voor Zaptec API (auth, charge history, chargers, rate limiting)
backend/src/services/zaptecSyncService.tsSync-orchestratie: per account sessies ophalen, dedupliceren, opslaan
backend/src/services/zaptecCredentialService.tsAES-256-GCM encryptie/decryptie van credentials
backend/src/routes/zaptecConnection.routes.tsREST endpoints: connect, disconnect, status, manual sync
backend/src/jobs/zaptecSyncJob.tsCron job: dagelijkse sync voor alle actieve koppelingen
frontend/src/pages/ZaptecConnectionPage.tsxKoppelpagina + beheer
frontend/src/components/dashboard/ZaptecSyncWidget.tsxDashboard-widget met sync-status

Te wijzigen bestanden

BestandWijziging
backend/prisma/schema.prismaNieuwe modellen: ZaptecConnection, ZaptecChargerLink, ZaptecSyncLog, enum ZaptecConnectionStatus
backend/src/server.tsRegistratie zaptecSyncJob
backend/src/app.tsMount zaptecConnection routes
backend/src/config/index.tsNieuwe config: ZAPTEC_CREDENTIAL_ENCRYPTION_KEY, ZAPTEC_SYNC_ENABLED
frontend/src/App.tsxRoute naar ZaptecConnectionPage
frontend/src/types/index.tsTypeScript types voor ZaptecConnection
frontend/src/pages/DashboardPage.tsxZaptecSyncWidget toevoegen

API Endpoints (Backend)

MethodePadBeschrijving
POST/api/v1/zaptec/connectValideer credentials, maak ZaptecConnection
DELETE/api/v1/zaptec/disconnectVerwijder koppeling en credentials
GET/api/v1/zaptec/statusKoppelstatus, laatste sync, linked chargers
POST/api/v1/zaptec/syncHandmatige sync trigger (rate limited)
PATCH/api/v1/zaptec/credentialsUpdate verlopen credentials
GET/api/v1/zaptec/chargersLijst van Zaptec-laders met match-status
POST/api/v1/zaptec/chargers/:zaptecChargerId/linkHandmatig koppelen aan ChargingPoint
DELETE/api/v1/zaptec/chargers/:zaptecChargerId/unlinkHandmatig ontkoppelen
GET/api/v1/admin/zaptec/connectionsAdmin: alle koppelingen met status

Sync-flow (Pseudocode)

FUNCTIE dagelijkseSync():
accounts = HAAL ALLE ZaptecConnections WHERE status = ACTIVE

VOOR ELKE account IN accounts (parallel, max 5):
syncLog = MAAK ZaptecSyncLog(account)

PROBEER:
token = haalOAuthToken(decrypt(account.credentials))

ALS token mislukt (401):
account.status = CREDENTIALS_EXPIRED
account.consecutiveFailures += 1
STUUR e-mail naar gebruiker
CONTINUE

chargerLinks = HAAL ZaptecChargerLinks VOOR account

VOOR ELKE link IN chargerLinks:
ALS link.chargingPointId IS NULL:
CONTINUE // Niet gekoppeld aan ERE laadpunt

sindsLaatstSync = link.lastSessionSync OF account.createdAt
sessies = haalChargeHistory(token, link.zaptecChargerId, from=sindsLaatstSync)

VOOR ELKE sessie IN sessies:
ALS ChargingSession BESTAAT MET externalId = sessie.id:
syncLog.sessionsDeduplicated += 1
CONTINUE

MAAK ChargingSession(
source: PLATFORM_ZAPTEC,
chargingPointId: link.chargingPointId,
userId/organizationId: VAN account,
startDateTime: sessie.startDateTime,
endDateTime: sessie.endDateTime,
kwhCharged: sessie.energy,
externalId: sessie.id,
period: FORMAT(sessie.startDateTime, "YYYY-MM")
)
syncLog.sessionsImported += 1

link.lastSessionSync = NU

// Aggregeer naar MonthlyKwhRecords
aggregeerSessiesNaarMaandRecords(account)

account.lastSyncAt = NU
account.lastSyncStatus = "SUCCESS"
account.consecutiveFailures = 0

BIJ FOUT:
account.consecutiveFailures += 1
syncLog.status = "FAILURE"

ALS account.consecutiveFailures >= 3:
account.status = PAUSED
STUUR admin-alert

8. Uitrolplan

Phase 1a: Infrastructuur & API Client (Sprint 1)

Werkzaamheden:

  1. Database-migratie: ZaptecConnection, ZaptecChargerLink, ZaptecSyncLog modellen
  2. zaptecCredentialService.ts: AES-256-GCM encryptie/decryptie
  3. zaptecApiClient.ts: OAuth token flow, charge history, chargers endpoints
  4. Rate limiting en exponential backoff implementatie
  5. Unit tests voor API client (gemockte responses)
  6. Integration tests voor credential service

Gate: API client kan succesvol authenticeren en sessies ophalen van Zaptec sandbox/test account.

Phase 1b: Sync Job & Routes (Sprint 2)

Werkzaamheden:

  1. zaptecSyncService.ts: sync-orchestratie, deduplicatie, aggregatie
  2. zaptecSyncJob.ts: cron job registratie
  3. zaptecConnection.routes.ts: connect, disconnect, status, manual sync
  4. Charger auto-matching logica
  5. Integration tests met database

Gate: End-to-end sync werkt: koppelen → sync → sessies in database → geaggregeerd naar MonthlyKwhRecord.

Phase 1c: Frontend & Admin (Sprint 3)

Werkzaamheden:

  1. ZaptecConnectionPage.tsx: koppelen, beheren, ontkoppelen
  2. ZaptecSyncWidget.tsx: dashboard-widget
  3. Admin-overzicht van koppelingen
  4. E-mailnotificaties bij credential-fouten
  5. E2E tests

Gate: Volledige flow testbaar op staging. Admin team review.

Phase 1d: Productie-uitrol (Week 4)

Werkzaamheden:

  1. ZAPTEC_CREDENTIAL_ENCRYPTION_KEY configureren in productie
  2. ZAPTEC_SYNC_ENABLED=true activeren
  3. Monitoring en alerting opzetten
  4. Uitrol naar selecte pilotgroep (5-10 gebruikers)
  5. Na 1 week zonder problemen: beschikbaar voor alle gebruikers

Gate: Pilotgroep succesvol gesynct gedurende 7 dagen. Geen data-inconsistenties.

Guardrails

MetriekDrempelActie
Sync-foutpercentage>10% van accountsOnderzoek; mogelijk API-wijziging bij Zaptec
Sync-duur>60 minutenPerformance-optimalisatie, eventueel parallellisatie verhogen
Credential-fouten>20% van accountsUX-review koppelpagina; communicatie naar gebruikers
Zaptec API-downtime>4 uurGebruikers informeren; sync hervatten na herstel

Kill-switch

  • Feature flag ZAPTEC_SYNC_ENABLED: Zet naar false om alle sync-jobs te pauzeren. Bestaande koppelingen blijven intact.
  • Per-account pauze: Individuele koppelingen kunnen worden gepauzeerd via admin-dashboard.
  • Tijdsinvestering: <5 minuten (environment variable wijziging).

9. Risico's & Open Vragen

Bekende Risico's

RisicoImpactKansMitigatie
Zaptec wijzigt API zonder waarschuwingHoogLaagMonitoring op sync-fouten; API versie pinnen; Zaptec statuspage volgen
Gebruikers vertrouwen ons hun Zaptec-wachtwoord niet toeHoogMiddelDuidelijke uitleg over encryptie en datagebruik; alleen read-access; mogelijkheid tot ontkoppelen
Zaptec rate limits te restrictief bij groei (>100 accounts)MiddelMiddelSync-window spreiden over meerdere uren; caching; eventueel Service Bus evalueren
ROPC grant type wordt door Zaptec uitgefaseerdHoogLaagMonitoren van Zaptec API changelog; architectuur voorbereiden op OAuth Authorization Code flow
Credential-lekrisico bij databasecompromisHoogLaagAES-256-GCM met externe sleutel; key rotation procedure; security audit
Dubbele sessies door overlap met handmatige uploadsMiddelHoogDeduplicatie op externalId; waarschuwing bij handmatige upload als Zaptec-sync actief is

Open Vragen

#VraagEigenaarDeadlineStatus
1Heeft Zaptec een partner/third-party OAuth flow (Authorization Code) als alternatief voor ROPC?EngineeringVoor Phase 1aOpen — ROPC vereist dat wij wachtwoorden opslaan, Authorization Code zou veiliger zijn
2Kunnen we een Zaptec sandbox/test account krijgen voor ontwikkeling?Engineering/SalesVoor Phase 1aOpen
3Moeten we gebruikers informeren over de koppelmogelijkheid via e-mail/notificatie?Product/MarketingVoor Phase 1dOpen
4Hoe gaan we om met Zaptec-accounts die aan meerdere installaties gekoppeld zijn (bijv. zakelijke klanten)?ProductVoor Phase 1bOpen — [Aanname: alle installaties worden gesynct]
5Is een verwerkersovereenkomst met Zaptec nodig voor het ophalen van sessiedata?JuridischVoor Phase 1dOpen
6Willen we op termijn ook andere platforms automatisch syncen (Alfen, Wallbox)?ProductNa Phase 1Open — beïnvloedt architectuurkeuzes

Afhankelijkheden

  • Zaptec API stabiliteit: API v1.0 is "stable" sinds januari 2025
  • Zaptec test-account: Nodig voor ontwikkeling en CI/CD
  • Encryption key management: Productie-infrastructuur moet een veilige key store bieden
  • Bestaande import-infra: ChargingSession model en platformImportService zijn de fundering

Aannames

  • [Aanname: Zaptec ROPC flow is de enige beschikbare auth-methode voor third-party integraties]
  • [Aanname: Charge history endpoint retourneert voldoende detail (kWh, start/eind, charger ID) voor ERE-claims]
  • [Aanname: Zaptec charger ID of serienummer is consistent genoeg voor automatische matching]
  • [Aanname: Dagelijkse sync (1x per 24 uur) is voldoende voor ERE-doeleinden — near-realtime is niet nodig]
  • [Aanname: <200 Zaptec-accounts in het eerste jaar — schaalbaarheid is beheersbaar]