PHP2Shell alert: critical PHP flaws CVE-2026-109618 / 109619 — check your servers and get patched fast
HashTech Systems
EV Charging
Part 5 of 10

OCPI Tokens Module: Whitelists and Real-Time Authorization

How eMSP tokens reach the CPO in OCPI 2.2.1, what the WhitelistType values mean, and how real-time authorization with AuthorizationInfo works.

HashTech Engineering · October 26, 2026 · 5 min read

When a driver taps an RFID card on a roaming charger, the CPO has a few hundred milliseconds to decide whether to start charging, and the card belongs to someone else's customer. The Tokens module is how the CPO knows about the eMSP's cards and app users, and how it asks the eMSP for a live decision when a cached answer is not enough.

This is part 5 of the OCPI 2.2.1 series. For the OCPP side of the same flow, see OCPP 1.6 Authorize and id tags and OCPP 2.0.1 authorization.

Who owns what

Tokens are owned by the eMSP. The eMSP implements the Tokens Sender interface (GET list and the real-time authorize endpoint); the CPO implements the Receiver interface, so the eMSP can push token changes to it. This is the reverse of Locations, and it is common to see both parties confused about it on day one.

The Token object

json
{
  "country_code": "NL",
  "party_id": "EXA",
  "uid": "04A1B2C3D4E5F6",
  "type": "RFID",
  "contract_id": "NL-EXA-C12345678-X",
  "visual_number": "EXA-1234-5678",
  "issuer": "Example eMSP",
  "group_id": "FLEET-ACME",
  "valid": true,
  "whitelist": "ALLOWED",
  "language": "nl",
  "last_updated": "2026-10-25T22:10:00Z"
}
FieldNotes
uidThe identifier the charger sees: for RFID, the card UID; for app users, an ID the eMSP chooses. Up to 36 characters.
typeTokenType: AD_HOC_USER, APP_USER, OTHER, RFID.
contract_idThe eMA/contract ID that identifies the contract for billing. Appears again in CDRs.
group_idGroups tokens of the same account, e.g. several cards on one fleet contract. Lets the CPO treat them as one for sessions.
validWhether the token may currently be used at all.
whitelistWhitelistType, see below.
default_profile_typeOptional ProfileType (CHEAP, FAST, GREEN, REGULAR) for smart charging preferences.

WhitelistType: the policy for offline decisions

The whitelist field is the eMSP telling the CPO how it may authorize this token:

ValueMeaningCPO behaviour
ALWAYSToken must always be whitelisted; real-time authorization is not possible or not allowed.Decide locally using the token's valid flag. Never call authorize.
ALLOWEDWhitelisting is allowed; real-time authorization is also allowed.CPO may choose. Typically: local decision for speed, real-time when available or required.
ALLOWED_OFFLINEUse real-time authorization normally; whitelist only if the eMSP cannot be reached.Call authorize; on timeout or connection failure, fall back to the local copy.
NEVERWhitelisting is forbidden.Always call authorize. If the eMSP is unreachable, do not start charging.

These map directly to OCPP concepts. Tokens with ALWAYS or ALLOWED are natural candidates for the charger's local authorization list (OCPP 1.6 SendLocalList), which lets the charger start sessions even when the CSMS is offline. NEVER tokens must not be sent to chargers at all. ALLOWED_OFFLINE sits in between: keep them in the CSMS's own store as a fallback, and decide per network whether they belong in chargers' local lists.

Getting tokens to the CPO

eMSP push

http
PUT /ocpi/cpo/2.2.1/tokens/NL/EXA/04A1B2C3D4E5F6?type=RFID HTTP/1.1
Host: cpo.example.com
Authorization: Token OWU4MGE1OWMtNmEyZi00YjVkLThlMWMtMmYzYTRiNWM2ZDdl
Content-Type: application/json
X-Request-ID: 5c4b3a29-1807-4f6e-8d5c-4b3a29180700
X-Correlation-ID: 5c4b3a29-1807-4f6e-8d5c-4b3a29180700

Blocking a lost card is a PATCH to the same URL with {"valid": false, "last_updated": "..."}. The type query parameter matters: the same uid can theoretically exist as both an RFID and an APP_USER token, so the key is (country_code, party_id, uid, type). When type is omitted, RFID is assumed.

CPO pull

The CPO can also page through the eMSP's tokens with GET {tokens_url}?date_from=...&offset=...&limit=.... Large eMSPs have millions of tokens, so a full initial pull can take a long time; use date_from incrementally after that, and do not re-pull everything nightly.

Real-time authorization

When the CPO needs a live decision, it posts to the eMSP's Sender interface:

http
POST /ocpi/emsp/2.2.1/tokens/04A1B2C3D4E5F6/authorize?type=RFID HTTP/1.1
Host: emsp.example.com
Authorization: Token ZWJmM2IzOTktNzc5Zi00NDk3LTliOWQtYWM2YWQzY2M0NGQy
Content-Type: application/json
X-Request-ID: 0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d
X-Correlation-ID: 0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d
json
{
  "location_id": "LOC-0042",
  "evse_uids": ["3256"]
}

The body is a LocationReferences object and is optional, but many eMSPs require it to apply location-specific rules. If the eMSP needs it and it is missing, it answers with status 2002. The response is an AuthorizationInfo:

json
{
  "data": {
    "allowed": "ALLOWED",
    "token": {
      "country_code": "NL",
      "party_id": "EXA",
      "uid": "04A1B2C3D4E5F6",
      "type": "RFID",
      "contract_id": "NL-EXA-C12345678-X",
      "issuer": "Example eMSP",
      "valid": true,
      "whitelist": "ALLOWED",
      "last_updated": "2026-10-25T22:10:00Z"
    },
    "authorization_reference": "AUTH-7f3c91",
    "location": { "location_id": "LOC-0042", "evse_uids": ["3256"] }
  },
  "status_code": 1000,
  "timestamp": "2026-10-26T10:14:58Z"
}
allowed (AllowedType)Meaning
ALLOWEDGo ahead.
BLOCKEDToken is blocked.
EXPIREDToken has expired.
NO_CREDITValid token, but the account has insufficient credit.
NOT_ALLOWEDValid token, but not allowed here or now (e.g. this location or EVSE is excluded).

An unknown token is not an AllowedType: the eMSP returns status code 2004 (Unknown Token), typically with HTTP 404. Treat it as a rejection. An optional info field carries a DisplayText the CPO can show on the charger screen if it has one.

authorization_reference

If the eMSP returns an authorization_reference, the CPO must put it in the resulting Session and CDR. It is how the eMSP ties a session back to a specific authorization decision, and some eMSPs reject or dispute CDRs that lack it. OCPP 1.6 has no field to carry it to the charger, so the CSMS must remember the mapping from (EVSE, token, time) to reference itself. This is covered more in Part 10.

Latency budget

A charger waiting on OCPP Authorize has a timeout, and the driver is standing there. A real-time authorize call that goes CPO, hub, eMSP and back can easily take a second or more. Practical measures:

  • Set a short, explicit timeout on the authorize call, and decide in advance what happens on timeout per whitelist value.
  • For ALWAYS and ALLOWED tokens, answer the charger from the local store first.
  • Keep HTTP connections to big partners warm (keep-alive, connection pooling).
  • Record every authorize result with its correlation ID; disputes about "the card was rejected" are resolved from these logs.

Field gotchas

  • RFID UID formatting. The same card can be read as 04A1B2C3D4E5F6, 04a1b2c3d4e5f6, or with byte order reversed, depending on the reader. Normalise to the format the eMSP publishes (most use uppercase hex) before lookup, and if you see systematic misses from one charger model, check byte order.
  • Length mismatch with OCPP 1.6. OCPP 1.6 idTag is limited to 20 characters; OCPI uid allows 36. App-user UIDs longer than 20 characters cannot be passed to a 1.6 charger as-is, which matters for remote starts.
  • Stale `valid` flags. If the CPO only pulls tokens nightly, a card blocked at 10:00 keeps working until the next sync. Ask eMSPs to push, and pull with date_from frequently.
  • Group IDs. If the eMSP sets group_id, a driver may start with one card and stop with another from the same group. Your stop logic should accept that.

Key takeaways

  • The eMSP owns tokens; the CPO receives them by push or pull, keyed on country_code, party_id, uid and type.
  • WhitelistType is binding policy: ALWAYS local only, ALLOWED either, ALLOWED_OFFLINE real-time with offline fallback, NEVER real-time only.
  • Real-time authorization returns AuthorizationInfo with an AllowedType; unknown tokens come back as status 2004.
  • Carry authorization_reference into sessions and CDRs.

Next: Part 6, Sessions and CDRs.

Let's Talk

Ready to build something that lasts?

Whether it's a charging network, an AI rollout, or a core enterprise system — let's talk about what you're building.