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, enKwhImportJobmodellen. 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:
| Aspect | Details |
|---|---|
| Base URL | https://api.zaptec.com/ |
| Authenticatie | OAuth 2.0 Resource Owner Password Credentials (ROPC) |
| Token endpoint | POST /oauth/token (grant_type=password, scope=openid) |
| Token geldigheid | 1 uur (3600 seconden) |
| Charge history | GET /api/chargehistory — voltooide sessies met filters |
| Chargers | GET /api/chargers — lijst van toegankelijke laders |
| Installaties | GET /api/installation — lijst van installaties |
| Rate limits | 10 req/s sustained, 15 burst; token endpoint 1 req/s/IP |
| Paginatie | Max 100 records per pagina |
| Push-optie | Azure Service Bus (AMQP) voor near-realtime events |
| API versie | v1.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)
| Metriek | Huidige Baseline | Doel | Tijdsbestek |
|---|---|---|---|
| Aantal Zaptec-koppelingen (connected accounts) | 0 | >30 actieve koppelingen | 6 weken na livegang |
| % Zaptec-gebruikers dat koppelt (vs. handmatig uploadt) | 0% | >50% van actieve Zaptec-gebruikers | 8 weken na livegang |
| Gemiddelde tijd tussen laadsessie en beschikbaarheid in portaal | Dagen tot weken (handmatig) | <24 uur (automatisch) | Week 1 na livegang |
| Sync-foutpercentage | N.v.t. | <2% van sync-runs | Continu |
Achterblijvende Indicatoren (bedrijfsresultaten)
| Metriek | Huidige Baseline | Doel | Tijdsbestek |
|---|---|---|---|
| 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 achterstand | Continu |
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,installationIdvoor 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
| Bestand | Doel |
|---|---|
backend/src/services/zaptecApiClient.ts | HTTP client voor Zaptec API (auth, charge history, chargers, rate limiting) |
backend/src/services/zaptecSyncService.ts | Sync-orchestratie: per account sessies ophalen, dedupliceren, opslaan |
backend/src/services/zaptecCredentialService.ts | AES-256-GCM encryptie/decryptie van credentials |
backend/src/routes/zaptecConnection.routes.ts | REST endpoints: connect, disconnect, status, manual sync |
backend/src/jobs/zaptecSyncJob.ts | Cron job: dagelijkse sync voor alle actieve koppelingen |
frontend/src/pages/ZaptecConnectionPage.tsx | Koppelpagina + beheer |
frontend/src/components/dashboard/ZaptecSyncWidget.tsx | Dashboard-widget met sync-status |
Te wijzigen bestanden
| Bestand | Wijziging |
|---|---|
backend/prisma/schema.prisma | Nieuwe modellen: ZaptecConnection, ZaptecChargerLink, ZaptecSyncLog, enum ZaptecConnectionStatus |
backend/src/server.ts | Registratie zaptecSyncJob |
backend/src/app.ts | Mount zaptecConnection routes |
backend/src/config/index.ts | Nieuwe config: ZAPTEC_CREDENTIAL_ENCRYPTION_KEY, ZAPTEC_SYNC_ENABLED |
frontend/src/App.tsx | Route naar ZaptecConnectionPage |
frontend/src/types/index.ts | TypeScript types voor ZaptecConnection |
frontend/src/pages/DashboardPage.tsx | ZaptecSyncWidget toevoegen |
API Endpoints (Backend)
| Methode | Pad | Beschrijving |
|---|---|---|
| POST | /api/v1/zaptec/connect | Valideer credentials, maak ZaptecConnection |
| DELETE | /api/v1/zaptec/disconnect | Verwijder koppeling en credentials |
| GET | /api/v1/zaptec/status | Koppelstatus, laatste sync, linked chargers |
| POST | /api/v1/zaptec/sync | Handmatige sync trigger (rate limited) |
| PATCH | /api/v1/zaptec/credentials | Update verlopen credentials |
| GET | /api/v1/zaptec/chargers | Lijst van Zaptec-laders met match-status |
| POST | /api/v1/zaptec/chargers/:zaptecChargerId/link | Handmatig koppelen aan ChargingPoint |
| DELETE | /api/v1/zaptec/chargers/:zaptecChargerId/unlink | Handmatig ontkoppelen |
| GET | /api/v1/admin/zaptec/connections | Admin: 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:
- Database-migratie: ZaptecConnection, ZaptecChargerLink, ZaptecSyncLog modellen
zaptecCredentialService.ts: AES-256-GCM encryptie/decryptiezaptecApiClient.ts: OAuth token flow, charge history, chargers endpoints- Rate limiting en exponential backoff implementatie
- Unit tests voor API client (gemockte responses)
- 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:
zaptecSyncService.ts: sync-orchestratie, deduplicatie, aggregatiezaptecSyncJob.ts: cron job registratiezaptecConnection.routes.ts: connect, disconnect, status, manual sync- Charger auto-matching logica
- 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:
ZaptecConnectionPage.tsx: koppelen, beheren, ontkoppelenZaptecSyncWidget.tsx: dashboard-widget- Admin-overzicht van koppelingen
- E-mailnotificaties bij credential-fouten
- E2E tests
Gate: Volledige flow testbaar op staging. Admin team review.
Phase 1d: Productie-uitrol (Week 4)
Werkzaamheden:
ZAPTEC_CREDENTIAL_ENCRYPTION_KEYconfigureren in productieZAPTEC_SYNC_ENABLED=trueactiveren- Monitoring en alerting opzetten
- Uitrol naar selecte pilotgroep (5-10 gebruikers)
- Na 1 week zonder problemen: beschikbaar voor alle gebruikers
Gate: Pilotgroep succesvol gesynct gedurende 7 dagen. Geen data-inconsistenties.
Guardrails
| Metriek | Drempel | Actie |
|---|---|---|
| Sync-foutpercentage | >10% van accounts | Onderzoek; mogelijk API-wijziging bij Zaptec |
| Sync-duur | >60 minuten | Performance-optimalisatie, eventueel parallellisatie verhogen |
| Credential-fouten | >20% van accounts | UX-review koppelpagina; communicatie naar gebruikers |
| Zaptec API-downtime | >4 uur | Gebruikers informeren; sync hervatten na herstel |
Kill-switch
- Feature flag
ZAPTEC_SYNC_ENABLED: Zet naarfalseom 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
| Risico | Impact | Kans | Mitigatie |
|---|---|---|---|
| Zaptec wijzigt API zonder waarschuwing | Hoog | Laag | Monitoring op sync-fouten; API versie pinnen; Zaptec statuspage volgen |
| Gebruikers vertrouwen ons hun Zaptec-wachtwoord niet toe | Hoog | Middel | Duidelijke uitleg over encryptie en datagebruik; alleen read-access; mogelijkheid tot ontkoppelen |
| Zaptec rate limits te restrictief bij groei (>100 accounts) | Middel | Middel | Sync-window spreiden over meerdere uren; caching; eventueel Service Bus evalueren |
| ROPC grant type wordt door Zaptec uitgefaseerd | Hoog | Laag | Monitoren van Zaptec API changelog; architectuur voorbereiden op OAuth Authorization Code flow |
| Credential-lekrisico bij databasecompromis | Hoog | Laag | AES-256-GCM met externe sleutel; key rotation procedure; security audit |
| Dubbele sessies door overlap met handmatige uploads | Middel | Hoog | Deduplicatie op externalId; waarschuwing bij handmatige upload als Zaptec-sync actief is |
Open Vragen
| # | Vraag | Eigenaar | Deadline | Status |
|---|---|---|---|---|
| 1 | Heeft Zaptec een partner/third-party OAuth flow (Authorization Code) als alternatief voor ROPC? | Engineering | Voor Phase 1a | Open — ROPC vereist dat wij wachtwoorden opslaan, Authorization Code zou veiliger zijn |
| 2 | Kunnen we een Zaptec sandbox/test account krijgen voor ontwikkeling? | Engineering/Sales | Voor Phase 1a | Open |
| 3 | Moeten we gebruikers informeren over de koppelmogelijkheid via e-mail/notificatie? | Product/Marketing | Voor Phase 1d | Open |
| 4 | Hoe gaan we om met Zaptec-accounts die aan meerdere installaties gekoppeld zijn (bijv. zakelijke klanten)? | Product | Voor Phase 1b | Open — [Aanname: alle installaties worden gesynct] |
| 5 | Is een verwerkersovereenkomst met Zaptec nodig voor het ophalen van sessiedata? | Juridisch | Voor Phase 1d | Open |
| 6 | Willen we op termijn ook andere platforms automatisch syncen (Alfen, Wallbox)? | Product | Na Phase 1 | Open — 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:
ChargingSessionmodel enplatformImportServicezijn 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]