Transactions Section — Migration Guide
Use cases E01–E17 (Transactions). Perspective: CSMS Developer.
Created by tzi.app — your guide to migrating from OCPP 2.0.1 to OCPP 2.1.
1. Summary of Changes
High-Level Overview
| Area | Change Type | Description |
|---|---|---|
| TransactionEventRequest | New fields | preconditioningStatus, evseSleep, costDetails added |
| TransactionType (transactionInfo) | New fields | operationMode, tariffId, transactionLimit added |
| TransactionEventResponse | New fields | transactionLimit, updatedPersonalMessageExtra added |
| TransactionEventResponse.totalCost | Semantics expanded | Can now be sent as running cost in Updated events, not just final cost in Ended |
| TriggerReasonEnumType | New values | CostLimitReached, LimitSet, OperationModeChanged, RunningCost, SoCLimitReached, TariffChanged, TariffNotAccepted, TxResumed added |
| ReasonEnumType (stoppedReason) | New value | ReqEnergyTransferRejected added |
| Connector status reporting | Message changed | StatusNotificationRequest replaced by NotifyEventRequest for connector state changes |
| E10 | New scenario | Timeout option added for permanently attached cables |
| E11 | Clarification | Explicit message-dropping order defined (intermediate messages first) |
| E12 | Clarification | Non-droppable triggerReasons specified for offline queued messages |
| E13 | Clarification | Specific retry configuration variables and algorithm documented |
| E15 | Scope narrowed | Now specifically covers ISO 15118 SessionStopReq(Terminate) flow |
| E16 | New use case | Transactions with Limits (cost, energy, time, SoC limits) |
| E17 | New use case | Resuming Transaction After Forced Reboot |
2. Data Type & Schema Changes
2.1 TransactionEventRequest — New Optional Fields
{
"eventType": "...",
"timestamp": "...",
"triggerReason": "...",
"seqNo": 0,
"transactionInfo": { ... },
"offline": false,
"numberOfPhasesUsed": 3,
"cableMaxCurrent": 32,
"reservationId": 456,
"evse": { ... },
"idToken": { ... },
"meterValue": [ ... ]
}{
"eventType": "...",
"timestamp": "...",
"triggerReason": "...",
"seqNo": 0,
"transactionInfo": { ... },
"offline": false,
"numberOfPhasesUsed": 3,
"cableMaxCurrent": 32,
"reservationId": 456,
"evse": { ... },
"idToken": { ... },
"meterValue": [ ... ],
"preconditioningStatus": "...", // NEW
"evseSleep": false, // NEW
"costDetails": { ... } // NEW
}| New Field | Type | Description |
|---|---|---|
preconditioningStatus | enum: Unknown, Ready, NotReady, Preconditioning | BMS preconditioning status for the EV battery |
evseSleep | boolean | true when EVSE electronics are in sleep mode |
costDetails | CostDetailsType | Cost calculated locally by the Charging Station |
Action Required: Add handling for preconditioningStatus if your CSMS needs to track battery conditioning state. Add handling for evseSleep if your
CSMS needs to track EVSE power state. Add handling for costDetails if you
want to compare CS-calculated costs with CSMS-calculated costs.
2.2 TransactionType (transactionInfo) — New Fields
{
"transactionId": "...",
"chargingState": "...",
"timeSpentCharging": 3600,
"stoppedReason": "...",
"remoteStartId": 123
}{
"transactionId": "...",
"chargingState": "...",
"timeSpentCharging": 3600,
"stoppedReason": "...",
"remoteStartId": 123,
"operationMode": "...", // NEW
"tariffId": "...", // NEW
"transactionLimit": { ... } // NEW
}| New Field | Type | Description |
|---|---|---|
operationMode | enum | Current operation mode of the transaction |
tariffId | string (max 60) | ID of the tariff currently in use |
transactionLimit | TransactionLimitType | Active limits on the transaction (see E16) |
Action Required: Store operationMode if your CSMS tracks charging modes. Store tariffId to link
transactions with the applied tariff. Implement transactionLimit handling for the new E16 use case.
2.3 TransactionEventResponse — New Fields
{
"totalCost": 12.50,
"chargingPriority": 0,
"idTokenInfo": { ... },
"updatedPersonalMessage": { ... }
}{
"totalCost": 12.50,
"chargingPriority": 0,
"idTokenInfo": { ... },
"updatedPersonalMessage": { ... },
"transactionLimit": { ... }, // NEW
"updatedPersonalMessageExtra": [ ... ] // NEW
}| New Field | Type | Description |
|---|---|---|
transactionLimit | TransactionLimitType | Set or update limits on the transaction from the CSMS side |
updatedPersonalMessageExtra | MessageContentType[] (max 4) | Additional messages to display to user |
TransactionLimitType New in 2.1
| Field | Type | Description |
|---|---|---|
maxCost | number | Max allowed cost in tariff currency |
maxEnergy | number | Max energy in Wh |
maxTime | integer | Max duration in seconds |
maxSoC | integer (0-100) | Max State of Charge percentage |
totalCost semantics change
| Behavior | OCPP 2.0.1 | OCPP 2.1 |
|---|---|---|
In Ended response | Final cost; omit = not free; 0.00 = free | Same |
In Updated response | Not specified | Running cost — can be sent during transaction to provide cost updates |
Action Required: Implement transactionLimit in responses to set/update limits on active transactions (required for E16). Consider
sending totalCost as
running cost in Updated responses (especially when maxCost limit is
active). Add support for updatedPersonalMessageExtra if your CSMS sends multiple user messages.
2.4 TriggerReasonEnumType — New Values
| Value | Version | Description |
|---|---|---|
CostLimitReached | 2.1 only | Cost limit on the transaction was reached |
LimitSet | 2.1 only | A limit was set on the transaction (by EV or CSMS) |
OperationModeChanged | 2.1 only | Operation mode of the transaction changed |
RunningCost | 2.1 only | Running cost update sent by the CS |
SoCLimitReached | 2.1 only | State of Charge limit was reached |
TariffChanged | 2.1 only | The tariff applied to the transaction changed |
TariffNotAccepted | 2.1 only | EV did not accept the tariff |
TxResumed | 2.1 only | Transaction resumed after an interruption (reboot/power loss) |
All existing 2.0.1 values remain unchanged: Authorized, CablePluggedIn, ChargingRateChanged, ChargingStateChanged, Deauthorized, EnergyLimitReached, EVCommunicationLost, EVConnectTimeout, EVDetected, EVDeparted, MeterValueClock, MeterValuePeriodic, TimeLimitReached, Trigger, UnlockCommand, StopAuthorized, RemoteStop, RemoteStart, AbnormalCondition, SignedDataReceived, ResetCommand.
Action Required: Add handling for all new trigger reasons in your TransactionEventRequest handler. CostLimitReached, LimitSet, SoCLimitReached are essential for E16. TxResumed is
essential for E17. TariffChanged, TariffNotAccepted, RunningCost support the new tariff and cost features.
2.5 ReasonEnumType (stoppedReason) — New Value
| Value | Version | Description |
|---|---|---|
ReqEnergyTransferRejected | 2.1 only | Requested energy transfer type was not granted |
All existing 2.0.1 values remain unchanged: DeAuthorized, EmergencyStop, EnergyLimitReached, EVDisconnected, GroundFault, ImmediateReset, Local, LocalOutOfCredit, MasterPass, Other, OvercurrentFault, PowerLoss, PowerQuality, Reboot, Remote, SOCLimitReached, StoppedByEV, TimeLimitReached, Timeout.
Action Required: Add ReqEnergyTransferRejected to your stoppedReason handling and billing logic.
3. Use Case Changes (E01–E15)
E01 — Start Transaction Options No Change
No functional changes. The core logic remains the same. The CSMS must
validate IdTokens and respond with idTokenInfo.
Schema changes (new optional fields in request/response) apply as documented in Section
2.
E02 — Start Transaction: Cable Plugin First Updated
Connector status notification changed
| Step | OCPP 2.0.1 | OCPP 2.1 |
|---|---|---|
| Cable plugged in | StatusNotificationRequest(connectorStatus = Occupied) | NotifyEventRequest(component.name = "Connector", variable.name =
"AvailabilityState", actualValue = "Occupied") |
| Response | StatusNotificationResponse {} | NotifyEventResponse {} |
The core transaction flow (TransactionEventRequest Started → Updated with Authorized → Updated with ChargingStateChanged) remains the same.
New functional requirements in 2.1
Meter values in Started event have context = Transaction.Begin (was implicit, now explicit)
Meter values in Updated events are periodic readings (was implicit, now explicit)
CS may split large meter data across multiple Updated messages with same timestamp (new clarification)
E03 — Start Transaction: IdToken First Updated
Connector status notification changed (same as E02 — see Section 5). No other functional changes. EVConnectTimeout handling remains the same.
E04 — Offline Start Transaction No Change
No functional changes. The offline = true flag behavior and retroactive processing remain the same.
E05 — Start Transaction: Id Not Accepted Clarified
Explicitly states that when StopTxOnInvalidId = true, CS stops energy transfer and sends Deauthorized trigger (was implied in 2.0.1, now explicit).
No other functional changes. The CSMS behavior is the same.
E06 — Stop Transaction Options No Change
No functional changes. All stop triggers and CSMS responsibilities remain the same.
E07 — Transaction Locally Stopped by IdToken Clarified
Added explicit statement that if the transaction is stopped NOT on driver request, CS
uses the appropriate ReasonEnumType value (was implied in 2.0.1).
The two-variant flow (direct Ended vs Update-then-Ended based on TxStopPoint) remains the same.
E08 — Transaction Stopped While Offline No Change
No functional changes. Offline stop handling remains the same.
E09 — Cable Disconnected on EV-side: Stop Transaction Updated
Connector status notification changed
| Step | OCPP 2.0.1 | OCPP 2.1 |
|---|---|---|
| Connector available | StatusNotificationRequest(status = Available) | NotifyEventRequest(component.name = "Connector", variable.name =
"AvailabilityState", actualValue = "Available") |
| Response | StatusNotificationResponse {} | NotifyEventResponse {} |
No other functional changes.
E10 — Cable Disconnected on EV-side: Suspend Transaction New Scenario
In OCPP 2.0.1, only two outcomes were documented: cable plugged back in (resume) or driver authorizes stop. OCPP 2.1 adds a third option:
// Option C (NEW in 2.1): Timeout (cable permanently attached)
[Receive] TransactionEventRequest
eventType = Ended,
stoppedReason = Timeout,
transactionId = "AB1234"
[Send] TransactionEventResponse { ... }Action Required: Handle stoppedReason = Timeout as a valid transaction end for suspended transactions with permanently attached cables.
E11 — Connection Loss During Transaction Clarified
When the CS runs low on memory, it drops intermediate messages first (2nd, 4th, 6th, etc.), never the first or last message of a transaction.
This was not specified in 2.0.1. The CSMS should expect sequence number gaps following this pattern when processing offline messages.
Action Required: Update sequence number gap detection logic to account for the intermediate-first dropping pattern.
E12 — Inform CSMS of Offline Occurred Transaction Clarified
When the CS drops non-critical Updated messages due to low memory, it SHALL NOT drop messages with these triggerReason values:
LimitSetCostLimitReachedEnergyLimitReachedTimeLimitReachedSoCLimitReachedSame intermediate-first dropping pattern as E11.FR.05.
Action Required: Be aware that limit-related trigger reasons are guaranteed to be present even when other intermediate messages are dropped.
E13 — Transaction-related Message Not Accepted by CSMS Clarified
OCPP 2.1 explicitly documents the retry behavior with specific configuration variables:
| Variable | Description |
|---|---|
MessageAttemptsTransactionEvent | Max number of retry attempts |
MessageAttemptIntervalTransactionEvent | Base interval between retries (seconds) |
Retry algorithm New in 2.1
1st failure: wait interval × 1, resend
2nd failure: wait interval × 2, resend
Nth failure: wait interval × N, resend
After MessageAttemptsTransactionEvent failures: discard message
This was not explicitly documented in 2.0.1. No CSMS code changes needed, but understanding the retry pattern helps with timeout and duplicate handling.
E15 — End of Charging Process Scope Narrowed
| Aspect | OCPP 2.0.1 | OCPP 2.1 |
|---|---|---|
| Scope | General "End of Charging Process" covering multiple interruption methods, remote stop, availability changes, and a full finalization checklist | Specifically covers ISO 15118 SessionStopReq(Terminate) flow |
| Interruption methods | Documented: ChangeAvailability, authorization revocation, RemoteStop | Not covered in E15 (moved to relevant sections) |
| Finalization checklist | Included in E15 | Not included in E15 |
Key 2.1 E15 behavior
When TxStopPoint contains Authorized/PowerPathClosed/EnergyTransfer, CS sends eventType = Ended with triggerReason = StopAuthorized and stoppedReason = StoppedByEV.
When TxStopPoint does NOT contain those values, CS sends eventType = Updated with triggerReason = StopAuthorized (transaction continues until TxStopPoint condition met).
No CSMS logic changes needed — the messages received are the same. The finalization checklist from 2.0.1 E15 is still valid practice, just not in E15 scope anymore.
4. New Use Cases in 2.1 (E16–E17)
E16 — Transactions with Limits New
This is an entirely new use case in OCPP 2.1. It enables both the EV Driver and the CSMS to set limits on a transaction.
Limit Types
| Limit Type | Field | Unit | Description |
|---|---|---|---|
| Cost | maxCost | tariff currency | Maximum allowed cost |
| Energy | maxEnergy | Wh | Maximum energy to deliver |
| Time | maxTime | seconds | Maximum transaction duration |
| State of Charge | maxSoC | % (0–100) | Maximum battery SoC |
Who Can Set Limits
| Source | How | Message |
|---|---|---|
| EV Driver (via EV) | ISO 15118 communication | CS sends TransactionEventRequest(triggerReason = LimitSet,
transactionInfo.transactionLimit = { ... }) |
| CSMS | Response to any TransactionEventRequest | CSMS includes transactionLimit in TransactionEventResponse |
CSMS Setting a Limit
// CSMS responds to any TransactionEventRequest with:
{
"idTokenInfo": { "status": "Accepted" },
"transactionLimit": {
"maxCost": 25.00
}
}
// CS confirms the limit in the next TransactionEventRequest:
// triggerReason = "LimitSet"
// transactionInfo.transactionLimit = { "maxCost": 25.00 }When a Limit is Reached
The CS sends a TransactionEventRequest with:
triggerReason = CostLimitReached / EnergyLimitReached / TimeLimitReached / SoCLimitReached
If TxStopPoint is NOT EnergyTransfer: eventType = Updated, chargingState = SuspendedEVSE
If TxStopPoint is EnergyTransfer: eventType = Ended
Key Requirements
| Req ID | Requirement |
|---|---|
E16.FR.02 | CSMS sets limits via transactionLimit in TransactionEventResponse |
E16.FR.03 | CS confirms limits by echoing them back in TransactionEventRequest |
E16.FR.04 | CS SHALL NOT exceed CSMS-set limits |
E16.FR.07 | CSMS can update limits by sending new values |
E16.FR.11 | When maxCost is active, CSMS SHALL provide cost updates via totalCost in responses |
E16.FR.12 | CSMS SHALL NOT send limit types not reported in TxCtrlr.SupportedLimits |
E16.FR.14 | If limit is increased after SuspendedEVSE, CS resumes charging |
E16.FR.17 | To remove a limit, set it to a very high value (limits cannot be unset) |
E16.FR.18 | Don't expect limit confirmation for offline messages |
Action Required: Check TxCtrlr.SupportedLimits before sending any transactionLimit in responses. Implement logic to set, update, and track limits on active transactions.
When maxCost is
active, provide running cost updates via totalCost in
TransactionEventResponse. Handle limit-reached trigger reasons. Store confirmed limits
echoed back by the CS.
E17 — Resuming Transaction After Forced Reboot New
This is an entirely new use case in OCPP 2.1. It handles the scenario where a Charging Station reboots unexpectedly (power loss, software fault) and can resume transactions.
A new configuration variable TxResumptionTimeout controls how long after a reboot the CS will attempt to resume a transaction. If the
interruption is within this timeout, the transaction resumes. If exceeded, the
transaction ends.
Scenario 1: Resume Within Timeout
[Receive] TransactionEventRequest
eventType = Updated,
triggerReason = TxResumed, // NEW trigger reason
transactionId = "AB1234",
transactionInfo: {
chargingState: <state before interruption>
}
[Send] TransactionEventResponse { ... }Scenario 2: End After Timeout Exceeded
[Receive] TransactionEventRequest
eventType = Ended,
triggerReason = AbnormalCondition,
transactionId = "AB1234",
stoppedReason = PowerLoss // or Reboot
[Send] TransactionEventResponse { ... }Key Requirements
| Req ID | Requirement |
|---|---|
E17.FR.14 | CS sends eventType = Updated, triggerReason = TxResumed with chargingState |
E17.FR.15 | CSMS SHALL resend SetChargingProfileRequest for TxProfile charging profiles when ChargingProfilePersistence for TxProfile is false or absent |
E17.FR.21 | If timeout exceeded due to power loss: stoppedReason = PowerLoss |
E17.FR.22 | If timeout exceeded due to software fault: stoppedReason = Reboot |
Action Required: Handle triggerReason = TxResumed in your TransactionEventRequest handler. After receiving TxResumed, check if you have
a TxProfile charging profile for this transaction and whether it persists across
reboots — if not, resend it via SetChargingProfileRequest. Handle stoppedReason = PowerLoss and stoppedReason = Reboot for transactions that could not be resumed. Store wasInterrupted flag on transactions for analytics/billing purposes.
5. Connector Status Notification Change
This is a cross-cutting change that affects E02, E03, E09, and any other flow that reports connector/EVSE availability.
| Aspect | OCPP 2.0.1 | OCPP 2.1 |
|---|---|---|
| Message | StatusNotificationRequest | NotifyEventRequest |
| Response | StatusNotificationResponse | NotifyEventResponse |
| Data model | connectorStatus field | Component/Variable pattern |
Example
CS -> CSMS: StatusNotificationRequest(
connectorStatus = "Occupied"
)
CSMS -> CS: StatusNotificationResponse {}CS -> CSMS: NotifyEventRequest(
component.name = "Connector",
variable.name = "AvailabilityState",
actualValue = "Occupied"
)
CSMS -> CS: NotifyEventResponse {}Action Required: If your CSMS handles StatusNotificationRequest for connector state during transaction flows, migrate to handling NotifyEventRequest with the Component/Variable pattern. The connector status values remain conceptually
the same (Occupied, Available, etc.).
6. Configuration Variable Changes
New Configuration Variables in 2.1
| Variable | Component | Description |
|---|---|---|
TxResumptionTimeout | TxCtrlr | Time (seconds) after reboot within which CS attempts to resume transactions (E17) |
SupportedLimits | TxCtrlr | List of supported limit types: maxEnergy, maxCost, maxTime, maxSoC (E16) |
ChargingProfilePersistence | TxCtrlr | Whether charging profiles persist across reboots (affects E17.FR.15) |
MessageAttemptsTransactionEvent | OCPPCommCtrlr | Max retry attempts for TransactionEventRequest (E13) |
MessageAttemptIntervalTransactionEvent | OCPPCommCtrlr | Base interval between retries in seconds (E13) |
Unchanged Configuration Variables
| Variable | Component | Description |
|---|---|---|
TxStartPoint | TxCtrlr | Defines when transaction starts |
TxStopPoint | TxCtrlr | Defines when transaction ends |
StopTxOnInvalidId | TxCtrlr | Whether to stop tx when Id becomes invalid |
MaxEnergyOnInvalidId | TxCtrlr | Max Wh to deliver with invalid Id |
StopTxOnEVSideDisconnect | TxCtrlr | Stop tx when cable disconnected at EV side |
UnlockOnEVSideDisconnect | TxCtrlr | Unlock connector when cable disconnected at EV side |
EVConnectionTimeOut | TxCtrlr | Timeout for EV to connect after authorization |
AuthorizeRemoteStart | AuthCtrlr | Whether to authorize remote start transactions |
MasterPassGroupId | AuthCtrlr | GroupId for Master Pass tokens |
7. Migration Checklist
Must Do Breaking / Functional
Handle NotifyEventRequest instead of StatusNotificationRequest for connector state changes during transaction flows.
Add all new TriggerReasonEnumType values to your TransactionEventRequest handler: CostLimitReached, LimitSet, OperationModeChanged, RunningCost, SoCLimitReached, TariffChanged, TariffNotAccepted, TxResumed.
Add ReqEnergyTransferRejected to your stoppedReason handling.
Handle triggerReason = TxResumed for transaction resumption after reboot.
Should Do New Capabilities
Implement transactionLimit in TransactionEventResponse to set/update limits.
Handle limit-reached trigger reasons and track confirmed limits from CS.
Check TxCtrlr.SupportedLimits before sending transactionLimit.
Send totalCost as running cost in Updated responses when maxCost limit is active.
Resend TxProfile charging profiles via SetChargingProfileRequest after transaction resume when profiles are not persisted (E17.FR.15).
Handle stoppedReason = Timeout for suspended transactions with permanently attached cables.
Nice to Have Optional Enhancements
Store preconditioningStatus for battery conditioning analytics.
Store evseSleep for EVSE power state tracking.
Store costDetails from CS and compare with CSMS-calculated costs.
Store operationMode and tariffId from transactionInfo.
Send updatedPersonalMessageExtra for multi-message display scenarios.
Update sequence gap detection to account for intermediate-first message dropping (E11.FR.05).
Track non-droppable trigger reasons when processing offline transaction batches (E12.FR.04).
Data Model Updates
Add the following fields to your Transaction data model:
Transaction (existing model):
+ tariffId: string (max 60, nullable) // NEW
+ transactionLimits: TransactionLimitType (nullable) // NEW
+ costDetails: CostDetailsType (nullable) // NEW
+ wasInterrupted: boolean (default false) // NEW (for E17 tracking)
TransactionEvent (existing model):
+ preconditioningStatus: enum (nullable) // NEW
+ evseSleep: boolean (nullable) // NEW