OCPP CSMS Smoke Test — Virtual Charge Point
Pricing
from $240.00 / 1,000 completed smoke runs
OCPP CSMS Smoke Test — Virtual Charge Point
A virtual OCPP 1.6J charge point connects to your own CSMS, plays boot, charging and responder scenarios and returns a step-by-step PASS/WARN/FAIL report. Built for CSMS developers and integrators. Authorized use only; not a certification, load or security tool. Pay per completed run.
Pricing
from $240.00 / 1,000 completed smoke runs
Rating
0.0
(0)
Developer
Andrew Boiko
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
OCPP CSMS Smoke Test - Virtual Charge Point
Connect a virtual OCPP 1.6J charge point to your own charging station management system (CSMS), play a real scenario - boot, heartbeat, a short charging session or a "stay connected and answer commands" window - and get a step-by-step PASS / WARN / FAIL report with request, response and latency of every message.
Authorized use only. Point this Actor only at a CSMS that you own or that you have explicit written permission to test. You must tick "I am authorized to test this CSMS" before anything runs; without it the Actor stops before any network connection is made. It is not a load, stress, fuzzing or security testing tool, it connects one virtual charge point, sends a handful of messages, and is limited to 3 runs per hour per target host. See Safe and responsible use.
Who is it for?
- CSMS vendors and in-house platform teams - run a quick end-to-end check of staging or production after each deploy ("can a charge point still boot, authorize, start and stop?").
- Charge point operators (CPOs) who want a repeatable test charge point without a physical wallbox.
- Integrators and QA teams - put it in CI before a heavier certification effort, or schedule it hourly as a synthetic monitor of your own OCPP endpoint.
- Developers building an OCPP backend who need a second opinion on handshake, subprotocol, response shapes and timeouts.
What it checks
| Step | OCPP message | What is verified |
|---|---|---|
| connect | WebSocket handshake | Sec-WebSocket-Protocol: ocpp1.6 negotiated (fails if the server picks another or none), TLS, Basic auth accepted, no redirect, handshake latency |
| BootNotification | BootNotification | status is Accepted / Pending / Rejected, currentTime is an ISO 8601 date-time (a missing time zone is only a warning), interval is an integer >= 0. Pending / Rejected give a warning and the remaining steps are skipped with an explanation |
| StatusNotification | StatusNotification (Available, Preparing, Charging, Available) | response is an empty object |
| Heartbeat | Heartbeat | currentTime is a valid date-time |
| Authorize | Authorize | idTagInfo.status is valid and Accepted for your test tag |
| StartTransaction | StartTransaction | transactionId is an integer, idTagInfo.status Accepted |
| MeterValues | MeterValues x2 | both are accepted with an empty-object response (the Actor sends two increasing Energy.Active.Import.Register samples; it does not check what the CSMS stores) |
| StopTransaction | StopTransaction (reason Local) | accepted, optional idTagInfo is well-formed. If a step fails after the transaction was started, the Actor still sends StopTransaction (and a final Available) when the connection is alive, so the test does not leave an open transaction in your CSMS |
| Responder window | CSMS-initiated calls | Reset, RemoteStartTransaction, RemoteStopTransaction, ChangeConfiguration, GetConfiguration, TriggerMessage, UnlockConnector, ChangeAvailability, DataTransfer, ClearCache are answered with plausible confirmations; anything else gets a CALLERROR NotImplemented. Every call is listed in received. Nothing is ever executed (TriggerMessage is only honoured in the responder scenario) |
For every message the Actor also detects: CALLERROR replies, 10 s timeouts, invalid JSON, wrong message ids or message types, wrong response shapes, a server that closes the connection mid-run, frames over 1 MB, and message floods (hard limits: 120 s, 200 messages, 1 MB total payload - the run is then closed cleanly with code 1000).
Scenarios
| Scenario | Steps |
|---|---|
connect | WebSocket handshake and subprotocol only |
boot | connect, BootNotification, StatusNotification(Available), Heartbeat |
basic-charge (default) | boot + Authorize, StatusNotification(Preparing), StartTransaction, StatusNotification(Charging), 2 x MeterValues over chargeSeconds, StopTransaction, StatusNotification(Available) |
responder | boot, then stay connected for responderSeconds and answer CSMS commands (trigger Reset, RemoteStart, TriggerMessage ... from your CSMS back office while the test runs) |
Not a certification tool
This Actor is a smoke test. It does not replace the official OCA OCTT (OCPP Compliance Testing Tool), which is the Open Charge Alliance's official test suite, and passing it does not make a CSMS "OCPP compliant" or certified. It is independent, unofficial and not affiliated with or endorsed by the Open Charge Alliance. It contains no OCA schema files; the response validators are small hand-written checks of required fields and types.
Safe and responsible use
- Consent is mandatory.
iAmAuthorizedmust betrue, otherwise the run ends before any network I/O and costs nothing. - Only TLS. Only
wss://endpoints on port 443 or 8000-9999 are accepted. No credentials inside the URL. - No internal targets. Loopback, private, link-local, carrier-grade NAT, multicast and cloud-metadata addresses (IPv4, IPv6 and IPv4-mapped) are refused. The host name is resolved once and the connection goes to exactly that address, so DNS rebinding cannot redirect the test to an internal host.
- No redirects are followed during the handshake.
- Rate limits (counted per Apify account, stored as hashes): at most 3 runs per hour per target host (host spelling, upper/lower case, a trailing dot, IPv6 notation and the port do not matter), 10 per hour per resolved server address (so many host names pointing to one server do not help) and 30 connections per hour in total. The first refused attempt returns
rate_limitedimmediately, without connecting and without charge. - Identifiable. Every handshake carries
User-Agent: ApifyOcppSmokeTest/1.0andX-Test-Source: apify-ocpp-smoke-test, so your operations team can recognise and filter the traffic. - Small and bounded. One connection, one charge point, a dozen messages. No fuzzing, flooding or load features, by design.
- Use a dedicated test charge point id and test idTag. Do not use the identity of a real station or a real customer's card.
How to use it
- Create a test charge point (and a test idTag) in your CSMS. If your CSMS needs authentication, create a temporary test account.
- Enter the endpoint, e.g.
wss://csms.example.com/ocpp, and the charge point ID. The Actor connects to<endpoint>/<chargePointId>(switch off Append the charge point ID if your URL already contains it). - Pick a scenario, tick I am authorized to test this CSMS, click Start.
- Read the report in the Report / Steps dataset views, the Markdown summary (
RESULTS_MD), or via API.
Example input - full charging session against a CSMS with Basic authentication
{"endpoint": "wss://csms.example.com/ocpp","chargePointId": "APIFY-SMOKE-01","authType": "basic","username": "APIFY-SMOKE-01","password": "temporary-test-password","scenario": "basic-charge","idTag": "TESTTAG1","connectorId": 1,"chargeSeconds": 5,"iAmAuthorized": true}
Example input - answer CSMS commands for 90 seconds
{"endpoint": "wss://csms.example.com:8443/ocpp/1.6","chargePointId": "VCP-RESPONDER-1","appendChargePointId": true,"scenario": "responder","responderSeconds": 90,"iAmAuthorized": true}
Try it with the prefilled input
The prefilled input points to wss://csms.example.invalid/ocpp, a host name that can never resolve. The run therefore ends within a few seconds with status unreachable (a "DNS lookup failed" report), is free, and still returns one non-empty dataset record so you can see the output format. Replace the endpoint with your own CSMS to run a real test.
Output
One dataset record per run (also available in the OUTPUT and RESULTS_MD records of the key-value store):
{"recordType": "smoke-run","status": "warnings","reason": "boot_not_accepted","message": "BootNotification: CSMS answered Pending ...","scenario": "basic-charge","endpointHost": "csms.example.com","chargePointId": "APIFY-SMOKE-01","ocppVersion": "1.6J","negotiatedSubprotocol": "ocpp1.6","handshakeMs": 84.3,"durationMs": 412,"summary": { "pass": 1, "warn": 1, "fail": 0, "skipped": 10 },"steps": [{"name": "BootNotification","action": "BootNotification","request": { "chargePointVendor": "ApifySmoke", "chargePointModel": "VCP-1" },"response": { "status": "Pending", "currentTime": "2026-10-02T12:00:00.000Z", "interval": 60 },"error": null,"latencyMs": 41.7,"result": "warn","notes": ["CSMS answered Pending: a real charge point must not send other messages until it is Accepted ..."]}],"received": [],"charged": { "event": "smoke-run", "units": 1 },"reportMarkdown": "# OCPP 1.6J smoke test: PASSED WITH WARNINGS ...","startedAt": "2026-10-02T12:00:00.000Z"}
status is one of passed, warnings, failed, unreachable, rejected_input, rate_limited, aborted. reason is a short machine-readable code (scenario_completed, step_failed, timeout, callerror, protocol_violation, connection_closed, subprotocol_mismatch, handshake_rejected_401, dns_failed, connect_failed, tls_error, message_limit, payload_limit, time_limit, consent_missing, blocked_target, rate_limited, budget ...).
Step results: pass, warn (works but unusual, e.g. Pending boot), fail (CSMS error or invalid response), skipped (not run because an earlier step failed).
Pricing
Pay per event: one smoke-run per run (current price and the discounts for paid Apify plans are shown on the Pricing tab), charged once per run, only when the WebSocket connection to your CSMS was actually established and the scenario finished or hit the first CSMS-side failure.
Always free: missing consent or invalid input, blocked targets, rate_limited, DNS failures, unreachable hosts, TLS or handshake failures before the connection is established, and the Actor's own errors. If your maximum cost per run is below the price of one run, nothing is started.
Integrations and API
Use it from CI right after a deployment and fail the pipeline when status is not passed.
curl -s "https://api.apify.com/v2/acts/YOUR_USERNAME~ocpp-csms-smoke-test/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-X POST -H 'Content-Type: application/json' \-d '{"endpoint":"wss://csms.example.com/ocpp","chargePointId":"APIFY-SMOKE-01","scenario":"boot","iAmAuthorized":true}'
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('YOUR_USERNAME/ocpp-csms-smoke-test').call({ endpoint: 'wss://csms.example.com/ocpp', chargePointId: 'APIFY-SMOKE-01', scenario: 'basic-charge', iAmAuthorized: true });const { items } = await client.dataset(run.defaultDatasetId).listItems();if (items[0].status !== 'passed') process.exit(1);
Schedule it hourly for a synthetic monitor of your own endpoint (mind the limit of 3 runs per hour per host), or connect it to Slack / e-mail / webhooks with Apify integrations.
FAQ
Does it work with my CSMS? With any OCPP 1.6J (JSON over WebSocket) central system that accepts a charge point over wss:// with the ocpp1.6 subprotocol, optionally with HTTP Basic authentication (security profile 1 / 2 over TLS).
What about my credentials? Create a temporary test account for the run. The password is an Apify secret input, is sent only in the Authorization header of the WebSocket handshake, and is never logged, stored in a dataset, key-value store or report, or returned in error messages (every output is scrubbed even if your CSMS echoes it back). Delete the test account afterwards. Do not put tokens in the URL path or query: unlike the password they are part of the endpoint you typed.
Why was my run rate_limited? More than 3 runs against the same host within one hour were started from your Apify account. Wait for the time shown in the report.
Why rejected_input? Consent missing, an endpoint that is not wss://, a port outside 443 / 8000-9999, credentials inside the URL, or a private / internal address.
Why is a failed connection sometimes free and sometimes not? If your CSMS never answered 101 Switching Protocols (DNS, TCP, TLS, HTTP 401/403/404/302) you are not charged. If it accepted the connection and then misbehaved (wrong subprotocol, CALLERROR, timeout, invalid response, closing mid-run) the test did its job and the run is charged.
Does it test my CSMS' security? No. It does not probe, fuzz or attack anything.
Limitations
- OCPP 1.6J only. OCPP 2.0.1 is on the roadmap.
- No client certificates (mTLS, security profile 3). Self-signed server certificates are not trusted.
- One connector and one transaction per run; no firmware, smart charging, reservation or local-list scenarios.
- Responses are checked for required fields and types only, not against the full official JSON schemas.
- The rate limits (3 runs per hour per target host, 10 per server address, 30 per account) are counted per Apify account. Connection attempts that fail (unreachable) also count.
Support
Open an issue from the Actor page with the reportMarkdown of the run (it contains no secrets) and what you expected.
More tools by the same author
- EV Charging Stations Scraper: Official open data on EU EV charging stations


