From Zero to OCPP: Launching a White-Label EV Charging Platform
Introduction
The EV charging market is growing fast — and the software layer underneath it is where the real competitive advantage lives. Whether you’re a mobility startup, an energy company, or an enterprise spinning up an internal fleet charging solution, building your own white-label OCPP platform gives you control, scalability, and brand ownership that off-the-shelf SaaS simply can’t offer.
In this guide, we’ll walk through everything you need to go from zero to a fully operational, white-label EV charging management system — covering protocol fundamentals, system architecture, backend implementation, branding layers, and go-to-market considerations.
1. What is OCPP and Why Does It Matter?
OCPP (Open Charge Point Protocol) is the de facto open standard for communication between EV charge points (hardware) and a Central System (your backend). Maintained by the Open Charge Alliance (OCA), it defines how chargers connect, authenticate, report status, and handle transactions.
Key versions to know
- OCPP 1.6 — The most widely deployed version. Uses SOAP or JSON over WebSocket. Still the industry baseline and supported by the vast majority of hardware vendors.
- OCPP 2.0.1 — The modern standard. JSON-only over WebSocket, with improved security profiles, smart charging, and device management. Required for newer compliance certifications.
Why build your own platform?
| Approach | Control | Cost at Scale | Brand Ownership |
|---|---|---|---|
| Off-the-shelf SaaS | Low | High | None |
| White-label reseller | Medium | Medium | Partial |
| Custom OCPP platform | Full | Low | Full |
If you expect more than a few hundred charge points or need deep integration with your product ecosystem, a custom platform is almost always the right long-term bet.
2. System Architecture Overview
A production-grade white-label OCPP platform consists of five core layers:
graph TD
A["🖥️ White-Label Frontend\nOperator Dashboard / Driver App"]
B["🔀 API Gateway Layer\nREST / GraphQL APIs"]
C["⚡ OCPP Central System\nWebSocket Server"]
D["⚙️ Business Logic & Event Bus\nSessions · Billing · Notifications"]
E["🗄️ Data & Infrastructure\nPostgreSQL · Redis · Kafka · S3"]
A -->|"Operator / Driver requests"| B
B -->|"OCPP commands"| C
C -->|"Charger events"| D
D -->|"Read / Write"| E
style A fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style B fill:#2E86C1,color:#fff,stroke:#1a5276
style C fill:#2874A6,color:#fff,stroke:#1a4f72
style D fill:#21618C,color:#fff,stroke:#154360
style E fill:#1B4F72,color:#fff,stroke:#0e2f44
Component breakdown
| Component | Responsibility | Recommended Tech |
|---|---|---|
| OCPP WebSocket Server | Handles charger connections | Node.js / Python (ocpp lib) |
| REST API | Operator & driver-facing endpoints | FastAPI / Express |
| Session Manager | Tracks active charging sessions | Redis + PostgreSQL |
| Event Bus | Async processing of charger events | Kafka / RabbitMQ |
| Billing Engine | Usage calculation, invoicing | Custom + Stripe |
| Notification Service | Alerts via email/SMS/push | SendGrid / Firebase |
| Frontend Dashboard | Operator white-label UI | React / Next.js |
| Driver Mobile App | End-user app (optional) | React Native / Flutter |
3. Setting Up the OCPP Central System
The Central System is the heart of your platform — it maintains persistent WebSocket connections with every charge point.
3.1 Choosing a WebSocket Library
For Node.js, the ocpp-j-1.6 or community ocpp packages are popular starting points. For Python, the ocpp library by mobilityhouse is production-tested and supports both 1.6 and 2.0.1.
# Python example using mobilityhouse/ocpp
pip install ocpp websockets
3.2 Basic Central System in Python
import asyncio
import websockets
from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16 import call_result
from ocpp.v16.enums import Action, RegistrationStatus
class ChargePoint(cp):
@on(Action.BootNotification)
async def on_boot_notification(self, charge_point_vendor, charge_point_model, **kwargs):
return call_result.BootNotificationPayload(
current_time=datetime.utcnow().isoformat(),
interval=10,
status=RegistrationStatus.accepted
)
@on(Action.Heartbeat)
async def on_heartbeat(self):
return call_result.HeartbeatPayload(
current_time=datetime.utcnow().isoformat()
)
async def on_connect(websocket, path):
charge_point_id = path.strip("/")
cp = ChargePoint(charge_point_id, websocket)
await cp.start()
async def main():
server = await websockets.serve(
on_connect,
"0.0.0.0",
9000,
subprotocols=["ocpp1.6"]
)
await server.wait_closed()
asyncio.run(main())
3.3 Key OCPP Messages to Implement
Core Profile (must-have)
BootNotification— Charger registers with your systemHeartbeat— Periodic keepaliveAuthorize— Token/RFID validationStartTransaction/StopTransaction— Session lifecycleMeterValues— Energy consumption reportingStatusNotification— Charger status changes
Remote Control (essential for operators)
RemoteStartTransaction— Start a session from the dashboardRemoteStopTransaction— Stop a session remotelyChangeAvailability— Enable/disable a connectorReset— Soft or hard reboot a charger
Smart Charging (OCPP 2.0.1 / advanced)
SetChargingProfile— Apply power limits or schedulesGetCompositeSchedule— Query current active schedule
4. Multi-Tenancy: The White-Label Core
White-labeling requires a robust multi-tenancy model. Each operator (tenant) should have:
- Their own branded domain (e.g.,
app.youroperator.com) - Isolated data (charge points, sessions, users)
- Custom branding (logo, colors, app name)
- Configurable pricing models
4.1 Tenant Data Model
-- Tenants (operators)
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL, -- e.g. "greenenergy"
name TEXT NOT NULL,
domain TEXT, -- custom domain
logo_url TEXT,
theme JSONB, -- { primary_color, font, etc. }
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Charge points belong to a tenant
CREATE TABLE charge_points (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES tenants(id),
ocpp_id TEXT UNIQUE NOT NULL, -- the ID used in WebSocket path
name TEXT,
location JSONB,
status TEXT DEFAULT 'Unknown',
created_at TIMESTAMPTZ DEFAULT NOW()
);
4.2 Routing Tenant Connections
When a charger connects via WebSocket, extract its OCPP ID from the path and look up which tenant it belongs to:
async def on_connect(websocket, path):
ocpp_id = path.strip("/")
# Resolve tenant from charge point registry
tenant = await db.get_tenant_by_ocpp_id(ocpp_id)
if not tenant:
await websocket.close(code=1008, reason="Unknown charge point")
return
cp = TenantAwareChargePoint(ocpp_id, websocket, tenant)
await cp.start()
5. Session Management and Billing
5.1 Session Lifecycle
sequenceDiagram
participant CP as Charge Point
participant CS as Central System
participant DB as Database
participant BE as Billing Engine
CP->>CS: Authorize(idTag)
CS->>DB: Validate token
DB-->>CS: Accepted
CS-->>CP: AuthorizeResponse(Accepted)
CP->>CS: StartTransaction(connectorId, idTag, meterStart)
CS->>DB: Open session record
CS-->>CP: StartTransactionResponse(transactionId)
loop Every 60s
CP->>CS: MeterValues(transactionId, energy)
CS->>DB: Store meter reading
end
CP->>CS: StopTransaction(transactionId, meterStop, reason)
CS->>DB: Close session
CS->>BE: Calculate cost
BE-->>CS: Invoice created
CS-->>CP: StopTransactionResponse
5.2 Storing Meter Values
@on(Action.MeterValues)
async def on_meter_values(self, connector_id, meter_value, **kwargs):
for reading in meter_value:
for sampled in reading.get("sampledValue", []):
await db.insert_meter_reading({
"session_id": self.active_session_id,
"timestamp": reading["timestamp"],
"measurand": sampled.get("measurand", "Energy.Active.Import.Register"),
"value": float(sampled["value"]),
"unit": sampled.get("unit", "Wh")
})
5.3 Pricing Models to Support
- Per kWh — Most common, based on energy delivered
- Per minute — Time-based billing (useful for parking enforcement)
- Session flat fee — Fixed price per charge
- Hybrid — e.g., $0.30/kWh + $0.05/min after 60 minutes
Store pricing as a JSONB config per tenant or per charge point for maximum flexibility.
6. Security and Authentication
6.1 Charge Point Authentication
OCPP 1.6 supports Basic Auth via the WebSocket URL:
ws://username:password@yourplatform.com/ocpp/CP001
For OCPP 2.0.1, use Security Profile 3 with TLS client certificates for production deployments.
6.2 Auth & RBAC Flow
flowchart LR
CP["⚡ Charge Point"] -->|"WSS + Basic Auth\nOCPP 1.6"| WS["WebSocket Server"]
CP2["⚡ Charge Point"] -->|"WSS + TLS Cert\nOCPP 2.0.1"| WS
OP["👤 Operator App"] -->|"HTTPS + JWT"| API["REST API"]
API -->|"RBAC check\nAdmin / Operator / Viewer"| BL["Business Logic"]
WS -->|"Forward events"| BL
style CP fill:#27AE60,color:#fff,stroke:#1e8449
style CP2 fill:#27AE60,color:#fff,stroke:#1e8449
style OP fill:#8E44AD,color:#fff,stroke:#6c3483
style WS fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style API fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style BL fill:#1B4F72,color:#fff,stroke:#0e2f44
- Use JWT tokens with tenant-scoped claims for operator APIs
- Implement RBAC (Admin, Operator, Viewer roles) per tenant
- Rate-limit all public-facing endpoints
- Audit-log every remote command sent to a charger
7. Building the White-Label Frontend
The operator dashboard is what your customers see every day. It needs to be fast, rebrandable, and functional.
7.1 Core Dashboard Features
- Live map — Real-time charger status on a map view
- Session monitor — Active sessions with energy and duration
- Charge point management — Add, configure, enable/disable chargers
- User management — RFID cards, driver accounts, access control
- Analytics — Revenue, utilization, energy dispensed over time
- Pricing configuration — Per-site or per-connector pricing rules
- Remote actions — Start/stop sessions, reboot chargers
7.2 Theming System
Use CSS variables + a tenant config endpoint to make theming seamless:
// Fetch tenant theme on app load
const { data: tenant } = await api.get('/api/v1/tenant/config');
document.documentElement.style.setProperty('--color-primary', tenant.theme.primary_color);
document.documentElement.style.setProperty('--color-secondary', tenant.theme.secondary_color);
document.title = tenant.name;
7.3 White-Label Deployment Options
| Option | Complexity | Isolation |
|---|---|---|
Subdomain per tenant (tenant.yourplatform.com) |
Low | Shared infra |
Custom domain with SSL (app.clientbrand.com) |
Medium | Shared infra |
| Dedicated deployment per tenant | High | Full isolation |
For most SaaS use cases, start with subdomain routing and add custom domain support via Nginx + Let’s Encrypt automation as you grow.
8. Infrastructure and Scaling
8.1 Starting Architecture (0–500 Chargers)
graph LR
CH["⚡ Chargers"] -->|"WebSocket"| WS["WebSocket Server\n1 node"]
OP["👤 Operators"] -->|"HTTPS"| API["REST API\n1-2 nodes"]
WS -->|"Read / Write"| PG[("PostgreSQL")]
WS -->|"Session cache"| RD[("Redis\nSession Cache")]
API -->|"Read / Write"| PG
API -->|"Store logs"| S3[("S3\nLogs / Reports")]
style CH fill:#27AE60,color:#fff,stroke:#1e8449
style OP fill:#8E44AD,color:#fff,stroke:#6c3483
style WS fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style API fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style PG fill:#1B4F72,color:#fff,stroke:#0e2f44
style RD fill:#C0392B,color:#fff,stroke:#922b21
style S3 fill:#D68910,color:#fff,stroke:#9a6709
8.2 Scaling to 10,000+ Chargers
At scale, the WebSocket layer becomes the bottleneck. Each charger maintains a persistent connection, so you need:
- Horizontal scaling of the WebSocket server with a shared session store (Redis)
- A message broker (Kafka or RabbitMQ) to fan out charger events to downstream services
- Connection routing so remote commands reach the right node holding the charger’s socket
graph TD
CH["⚡ Chargers"] -->|"WebSocket"| LB["Load Balancer"]
LB -->|"Sticky session"| WS1["WS Node 1"]
LB -->|"Sticky session"| WS2["WS Node 2"]
LB -->|"Sticky session"| WS3["WS Node N"]
WS1 & WS2 & WS3 -->|"Publish events"| KF[["Kafka\ncharger.events"]]
WS1 & WS2 & WS3 -->|"Register socket"| RD[("Redis\nConnection Registry")]
KF -->|"session.started/stopped"| SS["Session Service"]
KF -->|"meter.values"| BS["Billing Service"]
KF -->|"status.changed"| NS["Notification Service"]
SS & BS & NS -->|"Persist"| PG[("PostgreSQL")]
style CH fill:#27AE60,color:#fff,stroke:#1e8449
style LB fill:#566573,color:#fff,stroke:#2c3e50
style WS1 fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style WS2 fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style WS3 fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style KF fill:#E67E22,color:#fff,stroke:#ca6f1e
style RD fill:#C0392B,color:#fff,stroke:#922b21
style SS fill:#21618C,color:#fff,stroke:#154360
style BS fill:#21618C,color:#fff,stroke:#154360
style NS fill:#21618C,color:#fff,stroke:#154360
style PG fill:#1B4F72,color:#fff,stroke:#0e2f44
8.3 Observability Checklist
- Prometheus metrics on WebSocket connections, message latency, and error rates
- Structured JSON logging per charger/tenant
- Alerting on charger disconnection spikes
- Grafana dashboard for real-time charger health
9. OCPI Integration for Roaming
Once your platform is live, operators will want roaming — allowing drivers from other networks to charge at their stations. This is handled by OCPI (Open Charge Point Interface).
flowchart LR
DR["🚗 Driver\nPartner Network"] -->|"Auth Token"| EM["eMSP\nMobility Provider"]
EM -->|"OCPI 2.2.1"| HUB["OCPI Hub /\nAggregator"]
HUB -->|"CDRs / Locations / Tariffs"| YOUR["Your Platform\nCPO Role"]
YOUR -->|"OCPP"| CP["⚡ Charge Points"]
style DR fill:#27AE60,color:#fff,stroke:#1e8449
style EM fill:#8E44AD,color:#fff,stroke:#6c3483
style HUB fill:#E67E22,color:#fff,stroke:#ca6f1e
style YOUR fill:#1A6FBF,color:#fff,stroke:#0d4d8a
style CP fill:#1B4F72,color:#fff,stroke:#0e2f44
OCPI lets your platform:
- List locations and tariffs on public aggregators (PlugShare, ABRP, etc.)
- Accept payment from drivers on partner networks (e.g., Plug & Charge)
- Share real-time availability data with navigation apps
Implementing OCPI 2.2.1 as a CPO (Charge Point Operator) role is the typical starting point.
10. Go-to-Market Considerations
Positioning Your White-Label Platform
- Hardware-agnostic — Support any OCPP-compliant charger (this is your biggest selling point)
- Self-serve onboarding — Let operators add charge points without calling support
- Multi-currency billing — If targeting regional markets (Southeast Asia, EU, etc.)
- Compliance-ready — OCPP conformance certification from OCA adds credibility
Pricing Models for Your Platform
- Per charge point per month (most predictable)
- Revenue share on session fees (aligns incentives)
- One-time setup fee + annual license (enterprise)
Go-to-Market Roadmap
gantt
title Platform Launch Roadmap
dateFormat YYYY-MM-DD
section Phase 1 - Core
OCPP 1.6 Central System :p1, 2024-01-01, 30d
Basic session management :p2, after p1, 20d
Operator dashboard v1 :p3, after p1, 30d
section Phase 2 - Business
Multi-tenancy and billing :p4, after p2, 30d
White-label theming :p5, after p3, 20d
RFID and driver auth :p6, after p4, 15d
section Phase 3 - Scale
Smart charging OCPP 2.0.1 :p7, after p6, 30d
OCPI roaming integration :p8, after p6, 30d
Kafka horizontal scaling :p9, after p7, 20d
section Phase 4 - Growth
Mobile driver app :p10, after p8, 45d
Analytics and reporting :p11, after p9, 20d
Common Early Customers
- Real estate developers (condos, offices)
- Fleet operators (logistics, rental cars)
- Municipalities and parking operators
- Hospitality (hotels, resorts)
Conclusion
Building a white-label OCPP platform is a significant engineering undertaking — but it’s also a massive competitive moat once it’s live. The key milestones are:
- Get a working OCPP 1.6 Central System running with BootNotification, Authorize, and transaction handling
- Add multi-tenancy at the data and routing layer early — retrofitting it later is painful
- Build the operator dashboard with live status, remote actions, and basic analytics
- Layer in billing, smart charging, and OCPI as the platform matures
- Invest in observability — operators will call you the moment a charger goes offline
The EV charging software space is still early. Teams that ship fast, stay hardware-agnostic, and own their customer relationships through white-labeling will be in a strong position as the market accelerates.
Built with OCPP 1.6 / 2.0.1 · Open Charge Alliance · Python · Node.js · PostgreSQL · Redis
Get in Touch with us
Related Posts
- How to Build an EV Charging Network Using OCPP Architecture, Technology Stack, and Cost Breakdown
- Wazuh 解码器与规则:缺失的思维模型
- Wazuh Decoders & Rules: The Missing Mental Model
- 为制造工厂构建实时OEE追踪系统
- Building a Real-Time OEE Tracking System for Manufacturing Plants
- The $1M Enterprise Software Myth: How Open‑Source + AI Are Replacing Expensive Corporate Platforms
- 电商数据缓存实战:如何避免展示过期价格与库存
- How to Cache Ecommerce Data Without Serving Stale Prices or Stock
- AI驱动的遗留系统现代化:将机器智能集成到ERP、SCADA和本地化部署系统中
- AI-Driven Legacy Modernization: Integrating Machine Intelligence into ERP, SCADA, and On-Premise Systems
- The Price of Intelligence: What AI Really Costs
- 为什么你的 RAG 应用在生产环境中会失败(以及如何修复)
- Why Your RAG App Fails in Production (And How to Fix It)
- AI 时代的 AI-Assisted Programming:从《The Elements of Style》看如何写出更高质量的代码
- AI-Assisted Programming in the Age of AI: What *The Elements of Style* Teaches About Writing Better Code with Copilots
- AI取代人类的迷思:为什么2026年的企业仍然需要工程师与真正的软件系统
- The AI Replacement Myth: Why Enterprises Still Need Human Engineers and Real Software in 2026
- NSM vs AV vs IPS vs IDS vs EDR:你的企业安全体系还缺少什么?
- NSM vs AV vs IPS vs IDS vs EDR: What Your Security Architecture Is Probably Missing
- AI驱动的 Network Security Monitoring(NSM)













