OCPP 2.1 Edition 2 Section E

Transaction Flows - CSMS Developer Guide

Based on OCPP 2.1 Edition 2 Specification (Part 2), Section E (Transactions). This guide covers all transaction flows (E01–E17), including start/stop options, offline handling, cable disconnect, connection loss, transaction limits, resume after reboot, and remote transaction control from the CSMS perspective using OCPP-J (JSON over WebSocket).

22 Sections
17 Use Cases
E01 – E17

1. Overview & Core Concepts

Introduction

The Transactions functional block handles all transaction-related OCPP messaging. From the CSMS perspective, you must handle incoming transaction events, manage transaction lifecycles, track sequence numbers, and support remote start/stop operations.

What is a Transaction?

Single Time Frame

A transaction is the portion of a charging session recorded by the CSMS. It has a start and stop time, used by the operator for billing.

One per EVSE

At most one transaction can be active on an EVSE at any point in time.

CS-Generated IDs

Transaction IDs are generated by the Charging Station (not the CSMS). They are UUIDs (recommended), unique per CS for its entire lifetime.

Cannot Prevent Stop

The CSMS cannot prevent a transaction from stopping — it can only acknowledge the stop event.

Transaction Lifecycle

Every transaction follows this lifecycle communicated via TransactionEventRequest:

Transaction Lifecycle
Started  -->  Updated (0..N times)  -->  Ended

eventType = Started   → First message of a transaction
eventType = Updated   → Intermediate messages (meter values, state changes, authorization)
eventType = Ended     → Final message of a transaction

Flexible Start/Stop Points

The Charging Station is configured with TxStartPoint and TxStopPoint which determine WHEN a transaction starts/stops. The CSMS must understand these to correctly interpret messages.

TxStartPoint Values

Value Transaction Starts When
ParkingBayOccupancy Parking bay detector detects EV
EVConnected Cable is plugged in / communication established
Authorized EV Driver is authorized
DataSigned First signed meter values received
PowerPathClosed Charger ready to deliver power (authorized + connected)
EnergyTransfer Energy flow actually starts

TxStopPoint Values

Value Transaction Stops When
ParkingBayOccupancy EV leaves parking bay
EVConnected Cable is unplugged / communication lost
Authorized Authorization ends (e.g. IdToken presented again, CSMS deauthorizes)
PowerPathClosed Power path is opened (EV disconnected OR deauthorized)
EnergyTransfer Energy flow stops

OCPP 1.6 compatibility: Set TxStartPoint = PowerPathClosed and TxStopPoint = EVConnected, Authorized.

Sequence Numbers

The Charging Station maintains a per-EVSE counter (seqNo) for TransactionEventRequest messages:

  • seqNo SHOULD be 0 for eventType = Started
  • Incremented by 1 after each TransactionEventRequest
  • The CSMS uses seqNo to verify completeness
Completeness Check
Received Started with seqNo = a
Received Ended   with seqNo = o  (where o > a)

Must have received messages with seqNo = n
for every integer n between a and o

Key CSMS Responsibilities

  1. Always respond to TransactionEventRequest with TransactionEventResponse — never withhold a response (even for invalid data)
  2. Validate IdTokens in every TransactionEventRequest that contains one (the CS may have used outdated local cache)
  3. Track sequence numbers to detect missing messages
  4. Store transaction data for billing
  5. Send authorization status back when IdToken is present

2. Message Schemas Reference

Reference

TransactionEventRequest (CS → CSMS)

This is the primary inbound message the CSMS must handle for all transaction flows.

Required Fields

Field Type Description
eventType TransactionEventEnumType Started, Updated, or Ended
timestamp dateTime (ISO 8601) When the event occurred
triggerReason TriggerReasonEnumType Why this message was sent
seqNo integer (>= 0) Sequence number for completeness tracking
transactionInfo TransactionType Transaction details (always contains transactionId)

Optional Fields

Field Type Description
meterValue MeterValueType[] Array of meter readings
offline boolean true if event occurred while CS was offline (default: false)
numberOfPhasesUsed integer (0-3) Number of phases used for charging
cableMaxCurrent integer Max current of connected cable (Amps)
reservationId integer (>= 0) If this transaction ends a reservation
preconditioningStatus enum BMS preconditioning: Unknown/Ready/NotReady/Preconditioning
evseSleep boolean true when EVSE electronics are in sleep mode
evse EVSEType EVSE identifier (sent once, after EV connected)
idToken IdTokenType ID token (sent once, after authorization)
costDetails CostDetailsType Cost calculated locally by CS

TransactionType (transactionInfo object)

Field Required Description
transactionId Yes Unique transaction ID (UUID recommended, max 36 chars)
chargingState No EVConnected / Charging / SuspendedEV / SuspendedEVSE / Idle
timeSpentCharging No Seconds of actual energy transfer
stoppedReason No Why the transaction stopped (in Ended events)
remoteStartId No Matches RequestStartTransactionRequest.remoteStartId
operationMode No Current operation mode
tariffId No ID of tariff in use (max 60 chars)
transactionLimit No Active limits on the transaction

TriggerReasonEnumType Values

Value Description
Authorized EV Driver authorized
CablePluggedIn Cable was plugged in
ChargingRateChanged Charging rate changed
ChargingStateChanged Charging state changed
CostLimitReached Cost limit reached
Deauthorized EV Driver deauthorized
EnergyLimitReached Energy limit reached
EVCommunicationLost Communication with EV lost
EVConnectTimeout EV did not connect within timeout
EVDeparted EV departed from parking bay
EVDetected EV detected in parking bay
LimitSet A limit was set on the transaction
MeterValueClock Clock-aligned meter value
MeterValuePeriodic Periodic meter value
OperationModeChanged Operation mode changed
RemoteStart Remote start triggered
RemoteStop Remote stop triggered
ResetCommand Reset command received
RunningCost Running cost update
SignedDataReceived Signed meter data received
SoCLimitReached State of Charge limit reached
StopAuthorized Driver authorized stop
TariffChanged Tariff changed
TariffNotAccepted Tariff not accepted by EV
TimeLimitReached Time limit reached
Trigger Triggered by TriggerMessage
TxResumed Transaction resumed after interruption
UnlockCommand Unlock command received
AbnormalCondition Abnormal error or fault

ReasonEnumType (stoppedReason values)

Value Description
DeAuthorized CSMS revoked authorization
EmergencyStop Emergency stop activated
EnergyLimitReached Energy limit reached
EVDisconnected EV disconnected
GroundFault Ground fault detected
ImmediateReset Transaction ended by reset
Local Stopped locally by EV Driver (MAY be omitted)
MasterPass Master pass used to stop
Other Other reason
OvercurrentFault Overcurrent fault
PowerLoss Power loss
PowerQuality Power quality issue
Reboot Reboot
Remote Stopped remotely via RequestStopTransaction
SOCLimitReached SoC limit reached
StoppedByEV EV stopped charging (ISO 15118)
TimeLimitReached Time limit reached
Timeout Timeout
ReqEnergyTransferRejected Requested energy transfer type not granted
LocalOutOfCredit Local out of credit

ChargingStateEnumType

Value Description
EVConnected EV connected, not charging
Charging Active energy transfer
SuspendedEV Charging suspended by EV
SuspendedEVSE Charging suspended by EVSE
Idle No EV connected

TransactionEventResponse (CSMS → CS)

All fields are optional — an empty {} is a valid response.

TransactionEventResponse Schema
{
  "totalCost": "number",            // Running cost (Updated) or final cost (Ended)
                                    // 0.00 = free transaction. Absence != free.
  "chargingPriority": "integer",    // -9 to 9, default 0. Higher = higher priority.
  "idTokenInfo": {                  // MUST include when IdToken was in the request
    "status": "AuthorizationStatusEnumType",  // Accepted/Blocked/ConcurrentTx/Expired/...
    "cacheExpiryDateTime": "dateTime",
    "chargingPriority": "integer",
    "groupIdToken": { "idToken": "string", "type": "string" },
    "language1": "string (max 8)",
    "language2": "string (max 8)",
    "evseId": [1, 2],
    "personalMessage": { "format": "string", "content": "string" }
  },
  "transactionLimit": {             // Set/update limits on the transaction
    "maxCost": "number",
    "maxEnergy": "number (Wh)",
    "maxTime": "integer (seconds)",
    "maxSoC": "integer (0-100)"
  },
  "updatedPersonalMessage": { ... },       // Message to display to user
  "updatedPersonalMessageExtra": [ ... ]   // Additional messages (max 4)
}

Note: The TransactionEventResponse has NO required fields. An empty JSON {} is a valid response.

RequestStartTransaction (CSMS → CS)

Request

Field Required Description
remoteStartId Yes ID for matching. CS returns this in transactionInfo.remoteStartId
idToken Yes Token to use for the transaction
evseId No Specific EVSE to start on (integer >= 1)
groupIdToken No Group token
chargingProfile No Charging profile for the transaction

Response

Field Required Description
status Yes Accepted or Rejected
statusInfo No Additional status information
transactionId No If transaction already started (e.g. cable plugged in first)

RequestStopTransaction (CSMS → CS)

Field Required Description
transactionId Yes Transaction ID to stop (max 36 chars)

Response contains status (Accepted or Rejected) and optional statusInfo.

GetTransactionStatus (CSMS → CS)

Request

Field Required Description
transactionId No Specific transaction to query. If omitted, asks about any queued messages.

Response

Field Required Description
messagesInQueue Yes Whether there are still messages to be delivered
ongoingIndicator No Whether the transaction is still ongoing (only when transactionId was provided)

3. E01 — Start Transaction Options

Inbound
Use Case ID E01
Direction CS → CSMS (inbound)
Message TransactionEventRequest with eventType = Started
Purpose Covers all different moments a CS can start a transaction based on TxStartPoint
TransactionEventRequest — Started
{
  "eventType": "Started",
  "timestamp": "2025-01-15T10:30:00Z",
  "triggerReason": "<varies by scenario>",
  "seqNo": 0,
  "transactionInfo": {
    "transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "chargingState": "<varies>"
  },
  "evse": { "id": 1, "connectorId": 1 },
  "idToken": { "idToken": "AABB1234", "type": "ISO14443" },
  "meterValue": [...]
}

Scenarios and Expected triggerReasons

Scenario TxStartPoint triggerReason chargingState
S1: Parking bay detector ParkingBayOccupancy EVDetected (none)
S2: Cable plugged in EVConnected CablePluggedIn EVConnected
S3: Driver authorized Authorized Authorized (varies)
S4: Signed data received DataSigned SignedDataReceived (varies)
S5: Power path closed PowerPathClosed Authorized / CablePluggedIn EVConnected
S6: Energy flow starts EnergyTransfer ChargingStateChanged Charging
CSMS Response Example
{
  "idTokenInfo": {
    "status": "Accepted",
    "groupIdToken": { "idToken": "GROUP01", "type": "Central" }
  }
}

CSMS Handler Logic

handleTransactionEventStarted
function handleTransactionEventStarted(request):
    // 1. Store the new transaction
    transaction = createTransaction(
        transactionId: request.transactionInfo.transactionId,
        chargingStationId: connection.chargingStationId,
        startTime: request.timestamp,
        seqNo: request.seqNo
    )

    // 2. Store EVSE info if provided (E01.FR.16)
    if request.evse:
        transaction.evseId = request.evse.id
        transaction.connectorId = request.evse.connectorId

    // 3. If idToken is present, MUST validate it (E01.FR.11)
    response = {}
    if request.idToken:
        authResult = validateIdToken(request.idToken)
        response.idTokenInfo = {
            status: authResult.status,
            groupIdToken: authResult.groupIdToken  // if exists (E01.FR.12)
        }

    // 4. Store reservation link if present (E01.FR.13)
    if request.reservationId:
        transaction.reservationId = request.reservationId
        endReservation(request.reservationId)

    // 5. If remoteStartId present, link to original request (C05.FR.03)
    if request.transactionInfo.remoteStartId:
        linkRemoteStart(transaction, request.transactionInfo.remoteStartId)

    // 6. Store meter values if present
    if request.meterValue:
        storeMeterValues(transaction, request.meterValue)

    // 7. Track sequence number for completeness
    transaction.lastSeqNo = request.seqNo

    // 8. ALWAYS return a response
    return response

Important Rules for CSMS

  • E01.FR.11: CSMS SHALL verify the validity of the identifier in TransactionEventRequest
  • E01.FR.12: CSMS SHALL send idTokenInfo with authorization status AND groupIdToken if one exists
  • E01.FR.14: Only one transaction per EVSE+Connector at a time
  • The evse field is only provided ONCE (in the first TransactionEventRequest after EV connects) — cache it
  • The idToken field is only provided ONCE (in the first request after authorization) — cache it
  • Never withhold a response — this only causes retries

4. E02 — Start Transaction: Cable Plugin First

Inbound

Sequence: Cable plugged in first, then authorization happens. The transaction starts before the driver is identified.

Message Sequence (CSMS perspective)

Sequence Diagram
1. [Receive] NotifyEventRequest
     component.name = "Connector", variable.name = "AvailabilityState",
     actualValue = "Occupied"
   [Send]    NotifyEventResponse {}

2. [Receive] TransactionEventRequest
     eventType = Started,
     triggerReason = CablePluggedIn,
     chargingState = EVConnected,
     transactionId = "AB1234",
     seqNo = N,
     evse = { id: 1, connectorId: 1 },
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

   // At this point, driver has NOT been identified yet.
   // No idToken in this message.

3. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = Authorized,
     transactionId = "AB1234",
     seqNo = N + 1,
     idToken = { idToken: "1234", type: "ISO14443" },
     meterValue = [...]
   [Send]    TransactionEventResponse {
     idTokenInfo: { status: "Accepted" }
   }

4. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = ChargingStateChanged,
     transactionId = "AB1234",
     seqNo = N + 2,
     chargingState = Charging,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

CSMS Handler Logic

handleTransactionEvent_E02
function handleTransactionEvent_E02(request):
    tx = getTransaction(request.transactionInfo.transactionId)

    switch request.eventType:
        case "Started":
            // Transaction started without driver being known
            // E02.FR.06: When TxStartPoint contains EVConnected
            tx = createTransaction(...)
            // No idToken yet - cannot validate
            return {}

        case "Updated":
            if request.triggerReason == "Authorized" and request.idToken:
                // E02.FR.01: Driver identified - MUST validate
                // E02.FR.04: CSMS SHALL verify validity (might have been
                //            authorized locally with outdated info)
                authResult = validateIdToken(request.idToken)
                tx.idToken = request.idToken
                return {
                    idTokenInfo: {
                        status: authResult.status
                    }
                }

            if request.triggerReason == "ChargingStateChanged":
                tx.chargingState = request.transactionInfo.chargingState
                storeMeterValues(tx, request.meterValue)
                return {}

        case "Ended":
            finalizeTransaction(tx, request)
            return {}

Key CSMS Requirements (E02)

  • E02.FR.02: When CS sends TransactionEventRequest with idToken (triggerReason=Authorized), CSMS SHALL respond with idTokenInfo including authorization status
  • E02.FR.04: CSMS SHALL verify the identifier validity (even if CS authorized locally)
  • E02.FR.09: Meter values in Started event have context = Transaction.Begin
  • E02.FR.10: Meter values in Updated events are periodic readings
  • E02.FR.11: CS may split large meter data across multiple Updated messages with same timestamp

5. E03 — Start Transaction: IdToken First

Inbound

Sequence: Driver presents IdToken first, then plugs in cable. The transaction starts with authorization before cable connection.

Message Sequence (CSMS perspective)

Sequence Diagram
1. [Receive] TransactionEventRequest
     eventType = Started,
     triggerReason = Authorized,
     transactionId = "AB1234",
     seqNo = N,
     idToken = { idToken: "1234", type: "ISO14443" },
     meterValue = [...]
   [Send]    TransactionEventResponse {
     idTokenInfo: { status: "Accepted" }
   }

   // Driver now plugs in cable...

2. [Receive] NotifyEventRequest
     component.name = "Connector", variable.name = "AvailabilityState",
     actualValue = "Occupied"
   [Send]    NotifyEventResponse {}

3. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = CablePluggedIn,
     transactionId = "AB1234",
     seqNo = N + 1,
     chargingState = EVConnected,
     evse = { id: 1, connectorId: 1 }
   [Send]    TransactionEventResponse { ... }

   // Energy starts flowing...

4. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = ChargingStateChanged,
     transactionId = "AB1234",
     seqNo = N + 2,
     chargingState = Charging,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

EVConnectTimeout Handling

If the driver does NOT plug in the cable before EVConnectionTimeOut:

Timeout Sequence
[Receive] TransactionEventRequest
    eventType = Ended,
    triggerReason = EVConnectTimeout,
    transactionId = "AB1234",
    seqNo = N + 1,
    stoppedReason = Timeout
[Send]    TransactionEventResponse {}

CSMS Should:

  • Mark the transaction as ended
  • Not bill the driver (no energy was transferred)
  • Ensure sensible TxStartPoint/TxStopPoint combinations

Key CSMS Requirements (E03)

  • E03.FR.02: CSMS SHALL respond with idTokenInfo containing authorization status
  • E03.FR.03: If this transaction ends a reservation, next TransactionEventRequest SHALL contain reservationId
  • E03.FR.05: CS should end transaction on EVConnectTimeout when TxStopPoint does not contain ParkingBayOccupancy

6. E04 — Offline Start Transaction

Inbound (Delayed)

The Charging Station started a transaction while offline. When connection is restored, queued messages arrive with offline = true.

Message Sequence (CSMS perspective)

Sequence — Connection Restored
// Connection restored. CS sends queued messages:

1. [Receive] TransactionEventRequest
     eventType = Started,
     triggerReason = ...,
     transactionId = "AB1234",
     seqNo = N,
     offline = true,        // <-- KEY INDICATOR
     meterValue = [...],
     timestamp = "2025-01-15T08:00:00Z"  // actual event time
   [Send]    TransactionEventResponse { ... }

2. [Receive] TransactionEventRequest
     eventType = Updated,
     offline = true,
     seqNo = N + 1,
     ...
   [Send]    TransactionEventResponse { ... }

// ... more queued messages ...

CSMS Handler Logic

handleOfflineTransactionEvent
function handleOfflineTransactionEvent(request):
    // E04.FR.03: offline flag SHALL be true for events that occurred offline
    if request.offline:
        // Use request.timestamp as the actual event time
        // (NOT the time the message was received)
        tx = getOrCreateTransaction(request.transactionInfo.transactionId)
        tx.offlineOccurred = true

        // Process normally but note the offline flag
        // IdToken validation still applies
        if request.idToken:
            authResult = validateIdToken(request.idToken)
            return { idTokenInfo: authResult }

    return {}

Key CSMS Requirements (E04)

  • E04.FR.03: The offline flag SHALL be true for any event that occurred offline
  • Use request.timestamp as the actual event time, not the receive time
  • The CSMS should still validate IdTokens and respond normally
  • Multiple offline transactions may arrive in a batch

7. E05 — Start Transaction: Id Not Accepted

Inbound

The Charging Station locally authorized a driver, but when the CSMS validates the IdToken, it is NOT accepted. This is a critical flow for security.

Message Sequence (CSMS perspective)

Sequence Diagram
1. [Receive] TransactionEventRequest
     eventType = Started,
     transactionId = "AB1234",
     seqNo = N,
     evse = { id: 1, connectorId: 1 },
     idToken = { idToken: "AABB1234", type: "ISO14443" },
     meterValue = [...]

   // CSMS checks: this IdToken is Blocked/Invalid/Expired!
   [Send]    TransactionEventResponse {
     idTokenInfo: {
       status: "Blocked"   // or "Invalid", "Expired", "Unknown"
     }
   }

   // What happens next depends on CS configuration:

   // If StopTxOnInvalidId = false:
   2. [Receive] TransactionEventRequest
        eventType = Updated,
        triggerReason = ChargingStateChanged,
        chargingState = SuspendedEVSE,
        seqNo = N + 1
      [Send]    TransactionEventResponse { ... }

   // If StopTxOnInvalidId = true AND TxStopPoint = "EVConnected":
   2. [Receive] TransactionEventRequest
        eventType = Updated,
        triggerReason = Deauthorized,
        chargingState = EVConnected,
        seqNo = N + 1
      [Send]    TransactionEventResponse { ... }

   // If StopTxOnInvalidId = true AND TxStopPoint = "Authorized":
   2. [Receive] TransactionEventRequest
        eventType = Ended,
        triggerReason = Deauthorized,
        chargingState = EVConnected,
        stoppedReason = DeAuthorized,
        seqNo = N + 1
      [Send]    TransactionEventResponse { ... }

CSMS Handler Logic

handleTransactionEventWithIdValidation
function handleTransactionEventWithIdValidation(request):
    if request.idToken:
        // E05.FR.01: CSMS MUST verify validity
        authResult = validateIdToken(request.idToken)

        if authResult.status != "Accepted":
            // Return non-accepted status
            // CS will handle suspension/termination based on its config
            return {
                idTokenInfo: {
                    status: authResult.status  // "Blocked"/"Invalid"/"Expired"/etc
                }
            }

        return {
            idTokenInfo: { status: "Accepted" }
        }

    return {}

Key CSMS Requirements (E05)

  • E05.FR.01: CSMS MUST verify identifier validity (it might have been authorized locally with outdated info)
  • E05.FR.02: When StopTxOnInvalidId = false and MaxEnergyOnInvalidId is not exceeded: energy delivery is suspended but transaction continues
  • E05.FR.03: When StopTxOnInvalidId = false and MaxEnergyOnInvalidId is set: energy delivery allowed until that limit
  • E05.FR.09 (2.1): When StopTxOnInvalidId = true, CS stops energy transfer and sends Deauthorized trigger
  • E05.FR.10: When StopTxOnInvalidId = true and TxStopPoint contains (Authorized OR PowerPathClosed OR EnergyTransfer), CS sends eventType = Ended

8. E06 — Stop Transaction Options

Inbound

Covers all the different moments a Charging Station can stop a transaction based on TxStopPoint configuration.

Scenarios and Expected Messages

Scenario TxStopPoint triggerReason stoppedReason
S1: Parking bay vacated ParkingBayOccupancy EVDeparted Local
S2: Cable unplugged EVConnected EVCommunicationLost EVDisconnected
S3: Driver deauthorized Authorized Deauthorized / StopAuthorized DeAuthorized / Local
S5: EV/EVSE disconnected PowerPathClosed varies varies
S6: Energy stopped EnergyTransfer ChargingStateChanged varies
S7: Driver presents token Authorized / PowerPathClosed StopAuthorized Local
Generic Ended Event
{
  "eventType": "Ended",
  "timestamp": "2025-01-15T12:45:00Z",
  "triggerReason": "StopAuthorized",
  "seqNo": 5,
  "transactionInfo": {
    "transactionId": "AB1234",
    "chargingState": "EVConnected",
    "timeSpentCharging": 7200,
    "stoppedReason": "Local"
  },
  "idToken": { "idToken": "1234", "type": "ISO14443" },
  "meterValue": [
    {
      "timestamp": "2025-01-15T12:45:00Z",
      "sampledValue": [
        {
          "value": 15000,
          "context": "Transaction.End",
          "measurand": "Energy.Active.Import.Register",
          "unitOfMeasure": { "unit": "Wh" }
        }
      ]
    }
  ]
}

CSMS Handler Logic

handleTransactionEventEnded
function handleTransactionEventEnded(request):
    tx = getTransaction(request.transactionInfo.transactionId)

    // 1. Store final meter values
    if request.meterValue:
        storeMeterValues(tx, request.meterValue)

    // 2. Store stopped reason
    // E06.FR.08: stoppedReason is included if not ended by EV Driver
    // E06.FR.09: stoppedReason MAY be omitted when driver ended it locally
    //            (CSMS can interpret absence as "Local")
    tx.stoppedReason = request.transactionInfo.stoppedReason ?? "Local"
    tx.endTime = request.timestamp
    tx.timeSpentCharging = request.transactionInfo.timeSpentCharging

    // 3. Validate IdToken if present (driver presenting token to stop)
    response = {}
    if request.idToken:
        authResult = validateIdToken(request.idToken)
        response.idTokenInfo = { status: authResult.status }

    // 4. Check sequence completeness
    if tx.lastSeqNo + 1 != request.seqNo:
        // Missing messages! May need to use GetTransactionStatus
        flagIncompleteTransaction(tx)

    // 5. Calculate final cost if applicable
    if shouldCalculateCost(tx):
        response.totalCost = calculateFinalCost(tx)

    // 6. Mark transaction as completed
    tx.status = "Completed"

    return response

Key CSMS Requirements (E06)

  • E06.FR.08: If transaction is NOT ended by EV Driver, stoppedReason SHALL be present
  • E06.FR.09: If ended by EV Driver (local), stoppedReason MAY be omitted (interpret as Local)
  • E06.FR.11: Meter values in Ended event have context = Transaction.End
  • E06.FR.16: Abnormal errors/faults have triggerReason = AbnormalCondition
  • The CSMS cannot prevent a transaction from stopping

9. E07 — Transaction Locally Stopped by IdToken

Inbound

The EV Driver presents an IdToken to stop a transaction. This may be the same token or a different token with the same GroupId.

Direct Stop (TxStopPoint = Authorized/PowerPathClosed)

Sequence Diagram
1. [Receive] TransactionEventRequest
     eventType = Ended,
     triggerReason = StopAuthorized,
     transactionId = "AB1234",
     seqNo = N + 1,
     stoppedReason = Local,
     idToken = { idToken: "1234", type: "ISO14443" },
     meterValue = [...]
   [Send]    TransactionEventResponse {
     idTokenInfo: { status: "Accepted" }
   }

Update then End (TxStopPoint = EVConnected/ParkingBayOccupancy/EnergyTransfer)

Sequence Diagram
1. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = StopAuthorized,
     transactionId = "AB1234",
     seqNo = N + 1,
     idToken = { idToken: "1234", type: "ISO14443" }
   [Send]    TransactionEventResponse {
     idTokenInfo: { status: "Accepted" }
   }

   // Energy stops, cable unlocked, driver unplugs...

2. [Receive] TransactionEventRequest
     eventType = Ended,
     triggerReason = ChargingStateChanged,  // or EVCommunicationLost, EVDeparted
     transactionId = "AB1234",
     seqNo = N + 2,
     chargingState = EVConnected,
     stoppedReason = Local,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

Key CSMS Requirements (E07)

  • E07.FR.01: The same idToken OR an idToken with matching GroupId can stop the transaction
  • E07.FR.02: CS sends triggerReason = StopAuthorized with the stopping IdToken
  • E07.FR.04/05: stoppedReason is Local when stopped by EV driver at CS
  • E07.FR.06: If stopped NOT on driver request, CS uses appropriate ReasonEnumType value
  • The stopping idToken may differ from the starting idToken (they share GroupId)

10. E08 — Transaction Stopped While Offline

Inbound (Delayed)

A transaction stopped while the Charging Station was offline. When connection is restored, the CSMS receives the ended event with offline = true.

Message Sequence

Sequence — Connection Restored
// Connection restored:

1. [Receive] TransactionEventRequest
     eventType = Ended,
     transactionId = "AB1234",
     offline = true,
     timestamp = "2025-01-15T08:30:00Z",  // actual stop time
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

Key CSMS Requirements (E08)

  • E08.FR.05: Messages from offline period have offline = true
  • E08.FR.07: All messages from offline period have offline = true
  • Process the same as a normal stop but note the offline flag
  • Use timestamp from the message (actual time), not receive time

11. E09 — Cable Disconnected on EV-side: Stop Transaction

Inbound

Prerequisite: StopTxOnEVSideDisconnect = true. When the cable is disconnected at the EV side, the transaction is stopped immediately.

Message Sequence

Sequence Diagram
1. [Receive] TransactionEventRequest
     eventType = Ended,
     triggerReason = EVCommunicationLost,
     transactionId = "AB1234",
     stoppedReason = EVDisconnected,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

2. [Receive] NotifyEventRequest
     component.name = "Connector",
     variable.name = "AvailabilityState",
     actualValue = "Available"
   [Send]    NotifyEventResponse {}

CSMS Handler Notes

  • Treat as a normal transaction end
  • stoppedReason = EVDisconnected indicates cable was unplugged at EV side
  • Transaction is fully ended — no resumption

12. E10 — Cable Disconnected on EV-side: Suspend Transaction

Inbound

Prerequisite: StopTxOnEVSideDisconnect = false. Unlike E09, the transaction is suspended (not stopped) when cable is unplugged at EV side. The transaction can resume when cable is plugged back in.

Message Sequence

Sequence Diagram
// Cable unplugged at EV side:

1. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = EVCommunicationLost,
     transactionId = "AB1234",
     chargingState = SuspendedEV,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

// Option A: Cable plugged back in

2a. [Receive] TransactionEventRequest
      eventType = Updated,
      triggerReason = CablePluggedIn,
      transactionId = "AB1234",
      chargingState = Charging,
      meterValue = [...]
    [Send]    TransactionEventResponse { ... }

// Option B: Driver authorizes to end (cable not permanently attached)

2b. [Receive] TransactionEventRequest
      eventType = Ended,
      triggerReason = StopAuthorized,
      transactionId = "AB1234",
      meterValue = [...]
    [Send]    TransactionEventResponse { ... }

// Option C: Timeout (cable permanently attached)

2c. [Receive] TransactionEventRequest
      eventType = Ended,
      stoppedReason = Timeout,
      transactionId = "AB1234"
    [Send]    TransactionEventResponse { ... }

CSMS Handler Notes

  • Track the transaction state: SuspendedEV means cable disconnected at EV side
  • Transaction remains active until explicitly ended
  • No billing action until final Ended event

13. E11 — Connection Loss During Transaction

Inbound (Delayed)

The Charging Station loses connection during a transaction. The transaction continues on the CS. When connection is restored, queued messages arrive.

Message Sequence

Sequence — Connection Restored
// Connection restored:

1. [Receive] TransactionEventRequest
     eventType = Updated,
     offline = true,
     seqNo = ...,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

2. [Receive] TransactionEventRequest
     eventType = Updated,
     offline = true,
     seqNo = ...,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

// ... more queued messages ...

// Transaction may still be ongoing after queue is drained

Key CSMS Requirements (E11)

  • E11.FR.01: All queued messages have offline = true
  • E11.FR.07: All queued messages SHALL be set to Offline
  • Process all messages in order using their timestamp values
  • Check seqNo for completeness — CS may have dropped intermediate messages when running low on memory
  • E11.FR.05: CS drops intermediate messages first (2nd, 4th, 6th), never the first or last

14. E12 — Inform CSMS of Offline Occurred Transaction

Inbound (Delayed)

An entire transaction (start to end) occurred while the CS was offline. The CSMS receives all messages in a batch when connection is restored.

Message Sequence

Sequence — Connection Restored
// Connection restored:

1. [Receive] HeartbeatRequest
   [Send]    HeartbeatResponse { currentTime: "..." }

2. [Receive] TransactionEventRequest
     eventType = Started,
     offline = true,
     transactionId = "X",
     timestamp = "2025-01-15T03:00:00Z"
   [Send]    TransactionEventResponse { ... }

3. [Receive] TransactionEventRequest
     eventType = Updated,
     offline = true,
     transactionId = "X"
   [Send]    TransactionEventResponse { ... }

4. [Receive] TransactionEventRequest
     eventType = Ended,
     offline = true,
     transactionId = "X",
     timestamp = "2025-01-15T04:30:00Z"
   [Send]    TransactionEventResponse { ... }

CSMS Handler Logic

handleOfflineBatchTransaction
function handleOfflineBatchTransaction(request):
    // Process all messages with offline = true
    // Build the complete transaction picture from the batch

    // E12.FR.03: Meter values should be present
    // E12.FR.04 (2.1): CS may drop non-critical Updated messages when low on memory
    //   but NOT messages with triggerReason: LimitSet, CostLimitReached,
    //   EnergyLimitReached, TimeLimitReached, SoCLimitReached

    // E12.FR.05: Intermediate messages are dropped first (2nd, 4th, 6th)
    //   never the first or last

    // Process normally but note all messages are historical
    return processTransactionEvent(request)

Key CSMS Requirements (E12)

  • E12.FR.03: Meter values should be present in the batch
  • E12.FR.04: CS may drop non-critical Updated messages when low on memory, but NOT messages with triggerReason: LimitSet, CostLimitReached, EnergyLimitReached, TimeLimitReached, SoCLimitReached
  • E12.FR.05: Intermediate messages are dropped first (2nd, 4th, 6th) — never the first or last
  • All messages are historical — process normally but note they are from the past

15. E13 — Transaction-related Message Not Accepted by CSMS

Awareness

Describes how the Charging Station retries when the CSMS does not accept/respond to a message. The CSMS should be aware of this behavior.

Retry Behavior (CS-side, for CSMS awareness)

Retry Logic
1. CS sends TransactionEventRequest
2. If CSMS does not respond within MessageTimeout, CS retries
3. Wait time between retries:
   MessageAttemptIntervalTransactionEvent * attempt_number
4. Max retries: MessageAttemptsTransactionEvent
5. After final failure, CS discards the message

Example: Attempts = 3, Interval = 60
  1st failure: wait 60s, resend
  2nd failure: wait 120s, resend
  3rd failure: discard

CSMS Responsibilities

  • Always respond to TransactionEventRequest — failing to respond causes unnecessary retries
  • If the CSMS needs time to process, respond immediately and process asynchronously
  • Sanity check failures should never cause the CSMS to withhold a response

16. E14 — Check Transaction Status

Outbound

The CSMS can query whether a transaction is ongoing and whether there are queued messages. Use this after receiving an Ended event with sequence gaps, or to verify transaction state.

Use Cases

After Ended event

Check if there are missing intermediate messages

Verify active status

Check if transaction is still active

Check queued messages

Send without transactionId to check for any queued messages

Request/Response Examples

Query specific transaction
// CSMS sends:
{
  "transactionId": "AB1234"
}

// CS responds:
{
  "ongoingIndicator": true,  // transaction still active
  "messagesInQueue": true    // more messages to deliver
}
Query all queued messages
// CSMS sends:
{}

// CS responds:
{
  "messagesInQueue": false   // no queued messages
  // ongoingIndicator is NOT present when no transactionId specified
}

Decision Matrix

ongoingIndicator messagesInQueue Meaning
true true Transaction ongoing, messages pending
true false Transaction ongoing, all messages delivered
false true Transaction ended, messages still pending
false false Transaction ended, all messages delivered (or CS doesn't know this transaction)

CSMS Implementation

checkTransactionStatus
function checkTransactionStatus(transactionId = null):
    request = {}
    if transactionId:
        request.transactionId = transactionId

    response = sendToChargingStation(
        "GetTransactionStatusRequest",
        request
    )

    if response.messagesInQueue:
        // Wait for messages to be delivered before billing
        scheduleRetryCheck(transactionId)
    else if not response.ongoingIndicator:
        // Safe to start billing
        startBillingProcess(transactionId)

17. E15 — End of Charging Process

Inbound

Specific to ISO 15118 sessions. The EV sends a SessionStopReq(Terminate) message which causes the CS to end the transaction.

Message Sequence

Sequence Diagram
// ISO 15118 session stop occurs at EV/CS level

1. [Receive] TransactionEventRequest
     eventType = Ended,
     triggerReason = StopAuthorized,
     stoppedReason = StoppedByEV,
     meterValue = [...]
   [Send]    TransactionEventResponse { ... }

Key CSMS Requirements (E15)

  • E15.FR.04: When TxStopPoint contains "Authorized"/"PowerPathClosed"/"EnergyTransfer", CS sends eventType = Ended with triggerReason = StopAuthorized and stoppedReason = StoppedByEV
  • E15.FR.05: When TxStopPoint does NOT contain those values, CS sends eventType = Updated with triggerReason = StopAuthorized (transaction continues until TxStopPoint condition is met)

18. E16 — Transactions with Limits

Bidirectional New in 2.1

Either the EV Driver or CSMS can set limits on a transaction in terms of cost, energy, SoC, or time. This is critical for prepaid cards, direct payments, and energy management.

Scenario 1: EV Driver Sets Energy Limit

Sequence — Driver Sets Limit
1. [Receive] TransactionEventRequest
     eventType = Started,
     idToken = { idToken: "AA12345", type: "ISO14443" }
   [Send]    TransactionEventResponse {
     idTokenInfo: { status: "Accepted" }
   }

2. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = LimitSet,
     transactionInfo: {
       transactionId: "...",
       transactionLimit: { maxEnergy: 20000 }  // 20 kWh in Wh
     }
   [Send]    TransactionEventResponse {}

   // Charging continues until limit reached...

3. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = EnergyLimitReached,
     transactionInfo: {
       chargingState: "SuspendedEVSE"
     }
   [Send]    TransactionEventResponse {}

Scenario 2: CSMS Sets Cost Limit

Sequence — CSMS Sets Limit
1. [Receive] TransactionEventRequest
     eventType = Started,
     idToken = { idToken: "AA12345", type: "ISO14443" }
   [Send]    TransactionEventResponse {
     idTokenInfo: { status: "Accepted" },
     transactionLimit: { maxCost: 12.34 }    // <-- CSMS sets limit
   }

2. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = LimitSet,
     transactionInfo: {
       transactionLimit: { maxCost: 12.34 }  // CS confirms the limit
     }
   [Send]    TransactionEventResponse {}

   // Charging continues until cost limit reached...

3. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = CostLimitReached,
     transactionInfo: {
       chargingState: "SuspendedEVSE"
     }
   [Send]    TransactionEventResponse {}

CSMS Handler Logic for Limits

handleTransactionLimits
function handleTransactionLimits(request, response):
    tx = getTransaction(request.transactionInfo.transactionId)

    // E16.FR.02: CSMS sets limit via TransactionEventResponse
    if csmsWantsToSetLimit(tx):
        response.transactionLimit = {
            maxCost: getMaxCostForTransaction(tx)
        }

    // E16.FR.03: CS confirms limit by echoing it back
    if request.transactionInfo.transactionLimit:
        tx.confirmedLimits = request.transactionInfo.transactionLimit

    // E16.FR.04: CS SHALL NOT exceed CSMS-set limits

    // E16.FR.05/06: Handle limit reached
    if request.triggerReason in ["CostLimitReached", "EnergyLimitReached",
                                  "TimeLimitReached", "SoCLimitReached"]:
        // TxStopPoint NOT "EnergyTransfer": chargingState = SuspendedEVSE
        // TxStopPoint "EnergyTransfer": eventType = Ended
        tx.limitReached = request.triggerReason

    // E16.FR.07: CSMS can update limits
    if csmsWantsToUpdateLimit(tx):
        response.transactionLimit = { maxCost: newLimit }

    // E16.FR.11: When maxCost is active, CSMS SHALL provide cost updates
    if tx.confirmedLimits?.maxCost and not localCostCalculation:
        response.totalCost = calculateRunningCost(tx)

    // E16.FR.14: If limit increased after SuspendedEVSE, CS resumes charging

    // E16.FR.17: To remove a limit, set it to a very high value
    // (limits cannot be unset, only changed)

    // E16.FR.18: Don't expect limit confirmation for offline messages

Supported Limits Check

Before setting limits, check what the CS supports via TxCtrlr.SupportedLimits:

  • maxEnergy — energy limit
  • maxCost — cost limit
  • maxTime — time limit
  • maxSoC — state of charge limit

E16.FR.12: CSMS SHALL NOT send a transactionLimit that is not reported in TxCtrlr.SupportedLimits.

Key CSMS Requirements (E16)

  • E16.FR.02: CSMS sets limits via TransactionEventResponse.transactionLimit
  • E16.FR.03: CS confirms by echoing the limit back
  • E16.FR.07: CSMS can update limits in any response
  • E16.FR.08: Don't update if CS limit is already below CSMS requirement
  • E16.FR.11: When maxCost is active, CSMS SHALL provide cost updates
  • E16.FR.12: Don't send unsupported limit types
  • E16.FR.14: If limit increased after SuspendedEVSE, CS resumes charging
  • E16.FR.17: To remove a limit, set it to a very high value (limits cannot be unset)

19. E17 — Resuming Transaction After Forced Reboot

Inbound New in 2.1

When a Charging Station reboots unexpectedly (power loss, software fault), it can resume transactions if the interruption is within TxResumptionTimeout.

Scenario 1: Resume Within Timeout

Sequence — Power Restored Within Timeout
// Power loss at t=0, restored at t=10

1. [Receive] TransactionEventRequest
     eventType = Updated,
     triggerReason = TxResumed,
     transactionId = "AB1234",
     transactionInfo: {
       chargingState: <state before interruption>
     }
   [Send]    TransactionEventResponse { ... }

   // E17.FR.15: If CSMS has a TxProfile for this transaction,
   // CSMS SHALL resend it via SetChargingProfileRequest
   if hasChargingProfile(tx):
       sendSetChargingProfileRequest(tx.chargingProfile)

Scenario 2: End After Timeout Exceeded

Sequence — Timeout Exceeded
// Power loss occurred more than TxResumptionTimeout ago

1. [Receive] TransactionEventRequest
     eventType = Ended,
     triggerReason = AbnormalCondition,
     transactionId = "AB1234",
     stoppedReason = PowerLoss  // or Reboot
   [Send]    TransactionEventResponse { ... }

CSMS Handler Logic

handleTransactionResumed
function handleTransactionResumed(request):
    tx = getTransaction(request.transactionInfo.transactionId)

    if request.triggerReason == "TxResumed":
        // Transaction is resuming
        tx.wasInterrupted = true

        // E17.FR.15: Resend charging profile if applicable
        if hasTxProfile(tx) and not chargingProfilePersisted():
            sendSetChargingProfileRequest(tx.evseId, tx.chargingProfile)

        return {}

    if request.triggerReason == "AbnormalCondition":
        // Transaction ended due to timeout or failure
        tx.stoppedReason = request.transactionInfo.stoppedReason
        tx.endTime = request.timestamp
        finalizeTransaction(tx)
        return {}

Key CSMS Requirements (E17)

  • 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

20. Remote Transaction Control

Outbound

Remote Start Transaction (CSMS → CS)

The CSMS can remotely start a transaction on a Charging Station.

RequestStartTransactionRequest
// CSMS sends:
{
  "remoteStartId": 42,
  "idToken": {
    "idToken": "AABB1234",
    "type": "ISO14443"
  },
  "evseId": 1,                    // optional: specific EVSE
  "chargingProfile": { ... }      // optional: charging profile
}

// CS responds:
{
  "status": "Accepted",
  "transactionId": "existing-tx-id"  // if transaction already started
}
remoteStartTransaction Implementation
function remoteStartTransaction(chargingStationId, params):
    remoteStartId = generateUniqueId()

    request = {
        remoteStartId: remoteStartId,
        idToken: params.idToken
    }

    if params.evseId:
        request.evseId = params.evseId

    if params.chargingProfile:
        request.chargingProfile = params.chargingProfile

    response = sendToChargingStation(chargingStationId,
        "RequestStartTransactionRequest", request)

    if response.status == "Accepted":
        // Store remoteStartId to match with upcoming TransactionEventRequest
        // The CS will include remoteStartId in transactionInfo.remoteStartId
        storeRemoteStart(remoteStartId, chargingStationId, params)

        // If response.transactionId is present, transaction already started
        if response.transactionId:
            linkTransaction(remoteStartId, response.transactionId)

    return response

Remote Stop Transaction (CSMS → CS)

RequestStopTransactionRequest
// CSMS sends:
{
  "transactionId": "AB1234"
}

// CS responds:
{
  "status": "Accepted"
}

// Then CS sends:
// TransactionEventRequest(eventType = Ended, stoppedReason = Remote)
remoteStopTransaction Implementation
function remoteStopTransaction(chargingStationId, transactionId):
    response = sendToChargingStation(chargingStationId,
        "RequestStopTransactionRequest",
        { transactionId: transactionId })

    if response.status == "Accepted":
        // Wait for TransactionEventRequest with eventType = Ended
        // and stoppedReason = Remote
        return { success: true }

    return { success: false, reason: response.statusInfo }

21. Sequence Number Tracking & Completeness Validation

Reference

The CSMS must track sequence numbers per transaction to detect missing messages and ensure completeness before billing. This is especially important for offline scenarios where messages may be dropped.

Validation Algorithm

validateTransactionCompleteness
function validateTransactionCompleteness(transactionId):
    events = getTransactionEvents(transactionId)
                .sortBy(seqNo)

    startEvent = events.find(e => e.eventType == "Started")
    endEvent = events.find(e => e.eventType == "Ended")

    if not startEvent:
        return { complete: false, missing: "Started event" }

    if not endEvent:
        return { complete: false, missing: "Ended event" }

    startSeq = startEvent.seqNo
    endSeq = endEvent.seqNo

    // Check for gaps
    missingSeqs = []
    for n in range(startSeq, endSeq + 1):
        if not events.find(e => e.seqNo == n):
            missingSeqs.append(n)

    if missingSeqs.length > 0:
        // Use GetTransactionStatusRequest to check if messages are queued
        status = sendGetTransactionStatus(transactionId)

        if status.messagesInQueue:
            return { complete: false, missingSeqs, waitForDelivery: true }
        else:
            // Messages were lost (CS may have dropped them due to memory)
            return { complete: false, missingSeqs, messagesLost: true }

    return { complete: true }

When to use this:

  • After receiving TransactionEventRequest(eventType=Ended) where seqNo gaps are detected
  • When the CSMS wants to verify if a transaction is still ongoing
  • After connection restoration to check for pending messages

22. CSMS Implementation Checklist

Reference

Message Handlers to Implement

Priority Handler Direction Description
P0 TransactionEventRequest handler CS → CSMS Core: handle Started/Updated/Ended
P0 TransactionEventResponse builder CSMS → CS Core: build appropriate responses
P1 RequestStartTransactionRequest sender CSMS → CS Remote start capability
P1 RequestStopTransactionRequest sender CSMS → CS Remote stop capability
P1 GetTransactionStatusRequest sender CSMS → CS Query transaction status
P2 NotifyEventRequest handler CS → CSMS Connector availability changes

Data Model Requirements

Suggested Data Model
Transaction:
    transactionId: string (UUID, max 36)
    chargingStationId: string
    evseId: integer
    connectorId: integer
    startTime: datetime
    endTime: datetime (nullable)
    status: enum [Active, Completed, Cancelled]
    idToken: IdTokenType (nullable)
    stoppedReason: ReasonEnumType (nullable)
    timeSpentCharging: integer (seconds)
    lastSeqNo: integer
    startSeqNo: integer
    offline: boolean
    remoteStartId: integer (nullable)
    reservationId: integer (nullable)
    tariffId: string (nullable)
    transactionLimits: TransactionLimitType (nullable)
    meterValues: MeterValueType[]
    costDetails: CostDetailsType (nullable)

TransactionEvent:
    transactionId: string (FK)
    eventType: enum [Started, Updated, Ended]
    seqNo: integer
    timestamp: datetime
    triggerReason: TriggerReasonEnumType
    chargingState: ChargingStateEnumType (nullable)
    offline: boolean
    meterValues: MeterValueType[]
    receivedAt: datetime  // when CSMS actually received the message

Critical Business Rules

  1. ALWAYS respond to TransactionEventRequest — never withhold a response
  2. Validate every IdToken received in a TransactionEventRequest (E05.FR.01)
  3. Include idTokenInfo with groupIdToken in response when IdToken is in the request (E01.FR.12)
  4. Track sequence numbers per transaction to detect missing messages
  5. Use timestamp from the message for event times, not receive time (especially for offline events)
  6. Distinguish offline = true messages — they are historical events
  7. Check TxCtrlr.SupportedLimits before sending transactionLimit (E16.FR.12)
  8. Provide cost updates when maxCost limit is active (E16.FR.11)
  9. Resend TxProfile after transaction resume if not persisted (E17.FR.15)
  10. Use GetTransactionStatusRequest when sequence gaps are detected

Response Decision Matrix

eventType Has idToken? CSMS Response Should Include
Started Yes idTokenInfo (with status + groupIdToken)
Started No {} (empty) or transactionLimit if applicable
Updated Yes (Authorized) idTokenInfo (validate the token!)
Updated No, triggerReason = LimitSet Optionally transactionLimit to override
Updated No, periodic {} or totalCost for running cost
Ended Yes idTokenInfo + totalCost (final cost)
Ended No totalCost (final cost)

Error Handling

Malformed request

Still send a TransactionEventResponse (empty {} is valid). Log the error. Do NOT withhold the response.

Unknown transactionId

In Updated/Ended: create the transaction record and process. The Started message may have been lost.

Duplicate seqNo

Idempotent handling. The CS may be retrying a message.

Out-of-order seqNo

Buffer and reorder if possible. CS retries can cause reordering.

OCPP 2.1 Transaction Flows (E01–E17) - CSMS Developer Guide. Based on OCPP 2.1 Edition 2 Specification (Part 2), Section E.