This page is the integrator contract for the via-Hub session path: authorize → commands → sessions → CDRs. Role-specific endpoints: CPO, eMSP.
Always parse HTTP status, OCPI status_code, and payload fields (data.allowed, data.result, status_message) together. Authorize Hub-side failures often return OCPI status_code: 4002/4003 with data.allowed: UNKNOWN and HTTP 504 or 502.
RFID and Autocharge use the same Token module: realtime AUTH_REQUEST (CPO→Hub→eMSP authorize) or WHITELIST (eMSP token push). Remote app start remains COMMAND. Dedicated guide: RFID & Autocharge.
Sequence diagram
RFID / Autocharge at charger
See RFID & Autocharge for conventions, online authorize, whitelist sync, and partner checklists.
Whitelist path: eMSP PUT Token → Hub store-and-forward to CPO → local authorize → session with auth_method: WHITELIST.
Command failure branches
Step-by-step
1. Token authorization
CPO → eMSP (RFID / Autocharge at charger) — OCPI 2.2.1 Tokens Sender POST …/authorize:
CPO → Hub: POST /ocpi/2.2.1/tokens/{token_uid}/authorize?type=RFID
Headers: OCPI-to-country-code / OCPI-to-party-id = token-owning eMSP
Body: optional LocationReferences
Hub → eMSP: POST {emsp_tokens_sender_url}/{token_uid}/authorize?type=RFID
OCPI-To is required. type defaults to RFID when omitted. Hub returns full AuthorizationInfo (allowed, optional token, location, authorization_reference, info).
App / remote start uses Commands (START_SESSION), not Tokens authorize.
On Hub-side failure to reach or interpret the peer, Hub returns OCPI 4002/4003 with data.allowed: UNKNOWN and sets HTTP 504 (timeout) or 502 (unavailable / other) plus status_message.
2. Start session
eMSP → Hub: POST /ocpi/2.2.1/commands/START_SESSION
Hub → CPO: POST /ocpi/2.2.1/commands/START_SESSION
Required fields:
response_url— eMSP async CommandResult URL (Hub rewrites the URL sent to the CPO to a Hub callback)token— driver token objectlocation_id,evse_uid,connector_id— target charge point
Hub→CPO HTTP timeout for commands is 20 seconds. Immediate data.timeout (default 30) is the OCPI async-result window for the eMSP, not the Hub→CPO HTTP timeout.
3. Session becomes active
CPO → Hub: PUT /ocpi/2.2.1/sessions/{cpo_cc}/{cpo_pid}/{session_id}
You must set cdr_token.country_code and cdr_token.party_id to the eMSP. Hub enqueues async forward:
Hub → eMSP: PUT /ocpi/2.2.1/sessions/{cpo_cc}/{cpo_pid}/{session_id}
4. Stop session
eMSP → Hub: POST /ocpi/2.2.1/commands/STOP_SESSION
Include session_id in the body. Hub resolves the owning CPO from the stored session.
5. Session completed
CPO PUTs COMPLETED; Hub forwards to eMSP asynchronously.
6. CDR delivery
CPO → Hub: POST /ocpi/2.2.1/cdrs
Hub → eMSP: POST /ocpi/2.2.1/cdrs (async)
Required fields summary
| Field | Set by | Purpose |
|---|---|---|
cdr_token.country_code | CPO on session/CDR | Routes async delivery to eMSP |
cdr_token.party_id | CPO on session/CDR | Routes async delivery to eMSP |
response_url | eMSP on command | Async CommandResult delivery |
session_id | CPO / command flow | Links stop command to active session |
Timing expectations
| Path | Type | Contract |
|---|---|---|
| Token authorize | Sync | Hub waits on CPO; Hub does not retry |
| Commands | Sync + async | Immediate result; async via authenticated CPO callback → eMSP response_url; Hub→CPO HTTP timeout 20s |
| Sessions / CDRs | Async push | p95 < 5s Hub → eMSP; retries only for transient partner failures |
See Async Delivery.
Ownership and responsibilities
| Artifact | Set by | Hub uses for | Visible to |
|---|---|---|---|
Session path {cc}/{pid} | CPO | Owning CPO identity; eMSP receiver path | CPO; eMSP (as CPO key) |
session.id | CPO | Storage key; stop routing | CPO; routed eMSP |
cdr_token.{country_code,party_id} | CPO | Async target; eMSP GET /sessions scope | Target eMSP only |
Command response_url | eMSP | Rewritten to Hub callback, then forwarded | eMSP |
X-Request-ID / X-Correlation-ID | Callers / Hub | Support correlation | Both |
RACI
- CPO — authorize decision, immediate command
result, authenticated async CommandResult callback, session/CDR pushes with correctcdr_token. On charger/CSMS reconnect, finalize open transactions and pushCOMPLETED+ CDR (including abort/power-loss). - eMSP — tokens and commands; honor
data.timeoutwhen the CPO is silent; idempotent receivers; soft-close UX locally when sessions look stuck (Hub does not push provisional session status). - Hub — routing, store-and-forward, callback rewrite, credentialed forward to eMSP. May pull CPO sessions/CDRs to heal a stale Hub store (orphan reconcile). Does not invent sessions, final CommandResults,
COMPLETED, or CDRs.
Orphan / stale ACTIVE sessions
If a charger never stops (power loss, missed stop, CSMS gap), Hub may flag an orphan case for ops and pull the CPO for truth. After STOP is ACCEPTED/COMPLETED, Hub waits a 2 minute grace (ORPHAN_STOP_GRACE_MS) before stop_without_complete can open (swept about every 5 minutes). Hub still does not invent COMPLETED, CommandResult TIMEOUT, or CDRs, and does not auto-send another STOP_SESSION. eMSPs should still implement their own soft-close timers and customer messaging. Catch-up: GET /sessions and GET /cdrs. See the ops runbook docs/runbooks/orphan-sessions.md.
Sync vs async retry
Sync (authorize, commands to CPO): Hub does not retry. You may re-issue with your own backoff.
Async (session/CDR/catalog to eMSP): Hub retries timeout and unavailable only. Permanent failures (auth_error, not_found, client_error, invalid_response) are recorded and not retried. Domain write + outbox are durable; queue delivery is best-effort after commit.
Failure scenarios
Each card: Scenario → Hub Response → Retry → Expected Outcome → Owner.
CPO unreachable or timed out (command)
- Scenario:
START_SESSION(or other command); CPO commands endpoint does not answer within 20s, is unreachable, or Hub short-circuits because the circuit is open. PartyOFFLINEdoes not short-circuit the command. - Hub Response: HTTP
502, OCPIstatus_code2000,status_messageone of:"CPO command forward timed out""CPO command forward unavailable"(circuit or generic unavailable)"CPO command forward unauthorized"/"CPO command forward not found"/"CPO command rejected"/"CPO command forward failed"- Command record →
REJECTED. Hub does not leave the command pending for later CPO recovery.
- Retry: No (Hub). eMSP may re-issue after checking Connection Health.
- Expected Outcome: Treat start as failed unless a later re-issue succeeds.
- Owner: CPO (availability) + eMSP (retry policy).
Invalid token or authorization / command rejected by CPO
- Scenario: CPO blocks the token or rejects the command.
- Hub Response:
- Authorize:
data.allowed=BLOCKEDorNOT_ALLOWED(HTTP200, OCPI1000) when the CPO responds. - Commands:
data.result=REJECTED(HTTP200, OCPI1000) when the CPO responds.
- Authorize:
- Retry: No (until eligibility changes).
- Expected Outcome: Resolve token/eligibility before retrying.
- Owner: eMSP / driver.
Command ACCEPTED, charger never starts
- Scenario: Immediate
ACCEPTED, but no sessionPUTand no CommandResult callback. - Hub Response: Immediate
data.result: ACCEPTEDanddata.timeout(often30). Hub does not synthesize asyncTIMEOUT/REJECTED. - Retry: No (Hub).
- Expected Outcome: No session exists on Hub until CPO
PUTs one. eMSP must treat silence pasttimeoutas failure unless a callback or session arrives. CPO mustPOST/commands/callback/{command_id}with final CommandResult when start fails after accept. - Owner: CPO (callback + session) + eMSP (timeout handling).
Authorize: not routed or unknown CPO
- Scenario: No routing rule, or CPO party unknown.
- Hub Response: HTTP
200, OCPI1000,data.allowed: NOT_ALLOWED. This is not"Not routed"(that string is for commands/locations). - Retry: No until routing is enabled.
- Expected Outcome: Do not start charging for that pair.
- Owner: Routing configuration.
Authorize: CPO timeout
- Scenario: Hub aborts the authorize call to the CPO (timeout).
- Hub Response: HTTP
504, OCPI4002,data.allowed: UNKNOWN,status_message: "CPO authorize timeout". - Retry: No (Hub). eMSP may retry.
- Expected Outcome: Do not treat as a definitive token rejection.
- Owner: CPO + eMSP.
Authorize: CPO unavailable, circuit, or incomplete response
- Scenario: Open circuit, missing tokens endpoint, network/5xx/TLS failure, or unusable authorize payload. Party
OFFLINEdoes not short-circuit authorize. - Hub Response: HTTP
502, OCPI4003,data.allowed: UNKNOWN,status_messageone of:"CPO authorize unavailable""CPO tokens endpoint not configured""CPO authorize unauthorized"/"CPO authorize not found"/"CPO authorize rejected"/"CPO authorize response incomplete"
- Retry: No (Hub). eMSP may retry after health recovers.
- Expected Outcome: Same as timeout — not a definitive
NOT_ALLOWED. - Owner: CPO + eMSP.
Command not routed
- Scenario: No routing rule for the eMSP↔CPO pair.
- Hub Response: HTTP
403, OCPI2000,status_message: "Not routed". CPO is not called. - Retry: No until routing is enabled.
- Expected Outcome: Command fails at Hub.
- Owner: Routing configuration.
Unknown location or session on command
- Scenario: Unknown
location_idon start/reserve, or unknownsession_idon stop. - Hub Response: HTTP
404, OCPI2003,status_message: "Location not found". - Retry: No (fix IDs first).
- Expected Outcome: Command not forwarded.
- Owner: eMSP (IDs) or CPO (publish location / push session first).
Missing or wrong cdr_token on session or CDR
- Scenario: CPO push omits eMSP identity or sets the wrong party. A common migration mistake is setting
cdr_tokento Hub (IN/CIH) instead of the eMSP from the START_SESSIONtoken. - Hub Response: HTTP
200, OCPI1000to the CPO. Hub stores but does not enqueue to Hub (CIH).- COMPATIBILITY (active grace): Hub may rewrite the forward target from a matching
START_SESSION(authorization_reference, elsecdr_token.uid↔ commandtoken.uid) and deliver to that eMSP. Stored OCPI payloadcdr_tokenis left unchanged; routing columns use the resolved eMSP. CDRs can also inherit a previously rewritten session eMSP. - COMPLIANT or expired COMPATIBILITY / no matching START_SESSION: No forward is enqueued. Pull scoping also will not list the session under the real eMSP.
- COMPATIBILITY (active grace): Hub may rewrite the forward target from a matching
- Retry: Re-
PUT/ re-POSTwith correctcdr_token(eMSPcountry_code/party_idfrom the commandtoken). COMPATIBILITY rewrite is a migration aid, not a substitute for correct identity. - Expected Outcome: With a correct
cdr_token(or successful COMPATIBILITY rewrite), the target eMSP receives the push. Otherwise the eMSP never receives it. - Owner: CPO.
eMSP receiver down (async session or CDR)
- Scenario: CPO push already succeeded; Hub worker cannot deliver to the eMSP receiver.
- Hub Response: Not returned on the CPO's original call. Transient failures: Hub retries with backoff (may DLQ). Permanent partner errors: recorded and not retried (outbox
DEAD). - Retry: Yes (Hub) for timeout/unavailable only.
- Expected Outcome: Eventually delivered, or eMSP backfills via
GET /sessions/GET /cdrs, or support re-drive. - Owner: eMSP (receiver) + Hub support if stuck.
Wrong party GET session
- Scenario: Caller requests a session outside ownership /
cdr_tokenscope. - Hub Response: HTTP
404, OCPI2003,status_message: "Not found"(no ownership leak). - Retry: No.
- Expected Outcome: Use the correct party scope.
- Owner: Requesting party.
Command callback auth or eMSP forward failure
- Scenario: CPO posts async CommandResult, or Hub cannot reach eMSP
response_url. - Hub Response:
- Missing/wrong CPO token or CPO does not own the command → HTTP
401, OCPI2000. - eMSP forward failure → HTTP
502,"Failed to forward command callback to eMSP"or"... (timeout)".
- Missing/wrong CPO token or CPO does not own the command → HTTP
- Retry: No automatic Hub retry of the callback forward. CPO may retry the callback; keep
response_urlhealthy. - Expected Outcome: eMSP may miss async result; correlate with session push.
- Owner: CPO (auth + retry callback) + eMSP (
response_url).
Symptom index: Troubleshooting.
Hub response catalog
| Condition | HTTP | OCPI status_code | Payload / status_message | Hub retry? |
|---|---|---|---|---|
| Authorize not routed / unknown CPO | 200 | 1000 | allowed: NOT_ALLOWED | No |
| Authorize timeout | 504 | 4002 | allowed: UNKNOWN, "CPO authorize timeout" | No |
| Authorize unavailable / circuit / incomplete | 502 | 4003 | allowed: UNKNOWN + class message | No |
Authorize CPO ALLOWED / BLOCKED / NOT_ALLOWED | 200 | 1000 | allowed as returned | No |
| Command not routed | 403 | 2000 | "Not routed" | No |
| Command unknown location / session | 404 | 2003 | "Location not found" | No |
| Command circuit | 502 | 2000 | "CPO command forward unavailable" | No |
| Command forward timeout | 502 | 2000 | "CPO command forward timed out" | No |
| Command forward other partner failure | 502 | 2000 | Class-specific command message | No |
Command CPO ACCEPTED / REJECTED | 200 | 1000 | data.result (+ timeout) | No |
| Callback unauthenticated / wrong CPO | 401 | 2000 | Invalid token | No |
| Callback → eMSP failed | 502 | 2000 | Failed to forward… (timeout) when applicable | No |
Session / CDR stored (even missing cdr_token) | 200 | 1000 | Stored | N/A on CPO call |
| Async forward to eMSP failed | — | — | Not on CPO response | Transient only |
Wrong party GET session | 404 | 2003 | "Not found" | No |
| Rate limit | 429 | 2901 | "Rate limited" | Caller backs off |
Do not assume eMSP delivery from CPO success
Async delivery failures are not returned on the CPO's original PUT / POST. Use correlation IDs, pull backfill, and support re-drive when needed.
Role-specific next steps
| Role | What to implement |
|---|---|
| CPO | Sessions · CDRs · Tokens · Commands · Hub Receiver Identity |
| eMSP | Tokens · Commands · Sessions · CDRs · Hub Receiver Identity · Async Delivery |