Transactions are the part of OCPP that turns into money. Every invoice, every roaming CDR and every energy report depends on StartTransaction and StopTransaction arriving, being matched correctly, and carrying sensible meter readings. This part walks through both messages, the stop reasons, and how transaction messages behave when the charger is offline. It is part 5; part 4 covered authorization.
StartTransaction
[
2,
"tx-5501",
"StartTransaction",
{
"connectorId": 1,
"idTag": "04A2B3C4D5E680",
"meterStart": 1284560,
"timestamp": "2026-10-26T10:02:11Z"
}
]| Field | Type | Required | Notes |
|---|---|---|---|
connectorId | integer > 0 | Yes | Connector 0 is not valid here |
idTag | CiString20 | Yes | Identifier that started the transaction |
meterStart | integer | Yes | Energy register value in Wh at start |
timestamp | dateTime | Yes | When the transaction started on the charger |
reservationId | integer | No | Present when the transaction consumes a reservation |
meterStart is the absolute register reading of the connector's energy meter in Wh, not zero. Energy delivered is meterStop - meterStart. Do not assume the register starts at zero or increases across meter replacements.
The CSMS responds with a transactionId and the authorization result:
[
3,
"tx-5501",
{
"transactionId": 880213,
"idTagInfo": { "status": "Accepted" }
}
]The transactionId
The transactionId is an integer generated by the CSMS. The charger uses it in every later MeterValues and in StopTransaction for that session, and the CSMS uses it in RemoteStopTransaction. Generate it from a database sequence or similar source that will not repeat. It is an integer in the JSON schema, so keep it within 32-bit signed range: some charger firmware stores it as a 32-bit int, and IDs above 2,147,483,647 will break those chargers in confusing ways.
Always accept the transaction record
The CSMS cannot prevent a transaction by rejecting StartTransaction. By the time it arrives, the charger may already be delivering energy, and if the message was queued offline the session may already be over. The idTagInfo.status in the response tells the charger whether the identifier is valid, and the charger then acts according to its configuration:
StopTransactionOnInvalidIdtrue: the charger stops the transaction, andStopTransactionfollows with reasonDeAuthorized.StopTransactionOnInvalidIdfalse: the transaction stays open but the charger stops delivering energy (MaxEnergyOnInvalidId, where supported, caps how much energy may still be delivered after the identifier is invalidated).
Either way, always return a transactionId. A CSMS that responds with a CALLERROR to an unknown tag leaves the charger retrying the same message, and blocks the transaction queue behind it.
StopTransaction
[
2,
"tx-5530",
"StopTransaction",
{
"transactionId": 880213,
"idTag": "04A2B3C4D5E680",
"meterStop": 1311020,
"timestamp": "2026-10-26T11:14:52Z",
"reason": "Local",
"transactionData": [
{
"timestamp": "2026-10-26T10:02:11Z",
"sampledValue": [
{
"value": "1284560",
"context": "Transaction.Begin",
"measurand": "Energy.Active.Import.Register",
"unit": "Wh"
}
]
},
{
"timestamp": "2026-10-26T11:14:52Z",
"sampledValue": [
{
"value": "1311020",
"context": "Transaction.End",
"measurand": "Energy.Active.Import.Register",
"unit": "Wh"
}
]
}
]
}
]| Field | Required | Notes |
|---|---|---|
transactionId | Yes | From the StartTransaction response |
meterStop | Yes | Energy register in Wh at stop |
timestamp | Yes | When the transaction stopped |
idTag | No | Identifier that stopped it, if one was presented |
reason | No | Defaults to Local when omitted |
transactionData | No | Array of MeterValue; contents configured by StopTxnSampledData and StopTxnAlignedData |
The response contains an optional idTagInfo, relevant when an idTag was included, and is otherwise empty: [3, "tx-5530", {}]. As with start, respond even if the transactionId is unknown to you. Log it, create a reconciliation record, and answer, or the charger will keep retrying.
Stop reasons
| reason | When it is used |
|---|---|
DeAuthorized | The idTag was found invalid after the transaction started (for example, on the StartTransaction response) |
EmergencyStop | Emergency stop button pressed |
EVDisconnected | Cable unplugged from the vehicle |
HardReset | Hard reset command |
Local | Stopped locally by the user, for example by presenting the card again. Default when reason is omitted |
Other | Any other reason |
PowerLoss | Complete loss of power |
Reboot | Reboot not caused by a reset command (for example, a watchdog) |
Remote | Stopped by RemoteStopTransaction |
SoftReset | Soft reset command |
UnlockCommand | Stopped because of an UnlockConnector command |
Reasons are useful for analytics and support. A spike in EVDisconnected without a prior SuspendedEV on a particular model often points at a vehicle compatibility problem, and frequent Reboot stops point at firmware stability. Whether unplugging ends the transaction at all is controlled by StopTransactionOnEVSideDisconnect; if it is false, the session stays open while the cable is out until the user stops it.
Offline queuing and ordering
Transaction-related messages (StartTransaction, StopTransaction and MeterValues that belong to a transaction) are the messages the spec requires the charger to keep and deliver once it reconnects. Other messages, such as StatusNotification, may be discarded or summarised. Queued transaction messages must be delivered in the order they were generated.
If the charger is offline when a session starts, it does not have a transactionId yet. It keeps the session locally, and when it reconnects it sends StartTransaction first, gets the transactionId, and then uses it for the queued MeterValues and StopTransaction. A CSMS can therefore receive a start and a stop seconds apart for a session that took place hours ago. Use the charger's timestamp values for billing, not your receive times.
Delivery failures are governed by two keys: TransactionMessageAttempts (how many times to try) and TransactionMessageRetryInterval (seconds between attempts, with the spec describing an increasing back-off). A transaction message counts as failed if the CSMS returns a CALLERROR or does not answer; after the attempts are exhausted, the charger may drop it. That is another reason never to reject transaction messages with a CALLERROR for business reasons.
Common CSMS mistakes
- Opening duplicate sessions on retransmitted
StartTransactionmessages. - Rejecting
StopTransactionfor an unknowntransactionIdinstead of storing it for reconciliation. - Calculating energy from
MeterValuessamples instead ofmeterStop - meterStart. Samples are useful for graphs and smart charging; the start and stop registers are the billing source. (Part 6 covers meter values.) - Using receive time instead of charger timestamps, which makes offline sessions look like they lasted seconds.
- Leaving sessions open forever when a stop never arrives. Close stale sessions with an explicit status after a period, and keep them correctable if the stop turns up later.
Key takeaways
- The CSMS issues the
transactionId; keep it unique and inside 32-bit range. - Always respond to start and stop with a CALLRESULT. Use
idTagInfo.statusto communicate authorization, not errors. - Billing energy is
meterStop - meterStartin Wh. - Transaction messages are queued offline and delivered in order. Design for late, bunched-up delivery.
Previous: Part 4, Authorize and id tags. Next: Part 6, MeterValues. For how these records become roaming sessions and CDRs, see OCPI sessions and CDRs.

