> ## Documentation Index
> Fetch the complete documentation index at: https://developer.plugsurfing.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CDR forwarding

Receive Drive API CDR forwarding webhooks, handle retries idempotently, and process completed charging session records.

> ⚠️ CDR forwarding is only available when Plugsurfing does not clear the charging session. When Plugsurfing is the acting payment provider, the [session](/docs/drive-api/sessions#get-session-by-id) is used as a record of the charging session.

## Introduction

A CDR (Charging Detail Record) contains information about a completed charging session.

Overall, they contain the following information:

* Who: user or charging key information
* What: details on the charging session, including cost details
* Where: location details

CDRs are sent to you using [webhooks](https://en.wikipedia.org/wiki/Webhook).

For a webhook overview (including security and HMAC), start with [Webhooks](/docs/drive-api/webhooks).

## CDRs on Drive API

* Requests will be sent as `HTTP POST`
* The client should return a `200` response when the request is accepted
* The client should return a `400` response when the request is not valid
* The client should return a `403` response if authorization failed (see Configuration section below)
* The client should return a `500` response in case it fails
* We expect a response within 5 seconds. Any processing that can lead to response times higher than 5 seconds should be done asynchronously
* New fields might be added to the schema without prior notice. Ensure that unknown fields are ignored and do not result in errors.

### Retry logic

If the client doesn't respond with `200` or doesn't respond in time, the request will be retried: the CDR will be resent every night for 7 days (both on stage and prod environments).

#### Idempotency Expectation

For reliability, each webhook request includes a unique `requestId` that remains the same across all retry attempts. Therefore, your system should handle webhook events **idempotently**. This means:

* **Ensure your system processes each event only once** even if you receive the same `requestId` multiple times due to retries.
* **Return a`200` response** as soon as you have successfully processed the event or stored it for later processing.

#### Example Scenario:

* We send a request to your endpoint, your system experiences a delay and does not return a response in time so we mark it as failed, but your system eventually manages to process it.
* We will resend the same event again later, including the same `requestId`. At that point, we expect your system to recognize that this event has already been processed, so you should return a `200` response.

Handling retries idempotently helps avoid duplicate processing, ensuring consistent behavior across webhook interactions.

## Configuration

Here is what we need to configure the CDR webhook:

* URL to which we will send the CDRs
* Authorization headers that will be passed in the request to the mentioned URL

Please contact your account manager with this data, so that we can set it up.

## CDR Data

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| requestId | String | yes | requestId will not change upon retry |
| timestamp | String ISO date format | yes | example: **2022-12-12T12:55:21Z** (always UTC) |
| payer | Object ([Payer](/docs/drive-api/cdr#payer-model)) | yes | see [**Payer**](/docs/drive-api/cdr#payer-model) section below |
| type | String / Enum | yes | possible value: SESSION |
| item | Object ([Item](/docs/drive-api/cdr#item)) | yes | see [**Item**](/docs/drive-api/cdr#item) section below |

### Payer Model

There are two possible Payer models depending on the integration:

* if you create users using the [Users](/docs/drive-api/user) endpoints, it will be the [User Model](/docs/drive-api/cdr#user-model).
* if you create charging keys directly using the [EMP endpoints](/docs/drive-api/emp), it will be the [Charging Key Model](/docs/drive-api/cdr#charging-key-model).

#### User Model

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| type | String / Enum | yes | USER |
| id | String | yes | User id (provided during user creation) |
| chargingKey | Object | yes | |
| chargingKey.visualNumber | String | no | |
| chargingKey.uid | String | yes | |
| chargingKey.contractId | String | yes | |
| chargingKey.identificationType | String | yes | Possible values: `rfid`, `virtual` or `plug_and_charge` |

See [User model example](/docs/drive-api/cdr#user-model-example) for a sample JSON payload.

#### Charging Key Model

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| type | String / Enum | yes | CHARGING\_KEY |
| uid | String | yes | key identifier of the charging key paying |
| visualNumber | String | no | absent for virtual keys |
| contractId | String | yes | |
| identificationType | String | yes | Possible values: `rfid`, `virtual` or `plug_and_charge` |

See [Charging key model example](/docs/drive-api/cdr#charging-key-model-example) for a sample JSON payload.

### Item

#### Session

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| sessionId | String | yes | |
| externalSessionId | String | no | session id received from the CPO |
| startTime | String ISO date format | yes | session startTime, always UTC |
| stopTime | String ISO date format | yes | session stopTime, always UTC |
| energyConsumedInWh | Long | yes | total energy consumed during the session, in Wh |
| totalParkingTimeInSeconds | Long | no | total parking time in seconds (if available): duration during which the car was not charging. To calculate the total charging time:`stopTime - startTime - totalParkingTimeInSeconds` |
| empCost | Object ([Cost](/docs/drive-api/cdr#cost)) | yes | end-customer cost (“Cost” object described below) |
| cpoClaimedCost | Object ([Cost](/docs/drive-api/cdr#cost)) | yes | cost received from the CPO |
| location | Object ([Location](/docs/drive-api/cdr#location)) | yes | |
| homeCharging | Object ([HomeCharging](/docs/drive-api/cdr#homecharging)) | no | Only present in the case of homeCharging |
| startMethod | String | yes | Indicates how the charging session was started.<br /><br />Possible values:<br />`REMOTE`: the session was initiated by a backend/app command to the charge point.<br />`LOCAL`: the session was initiated directly at the charge point, for example via RFID or Plug\&Charge. |

##### Location

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| id | String | yes | locationId |
| evseId | String | yes | e.g. IT\_TSL\_EMIL\*0001 |
| cpoId | String | yes | operator's external id (e.g. "DE\*ABC") |
| cpoName | String | yes | operator's name (same as in geo search `Location.operator_display_name`) |
| address | String | yes | |
| city | String | yes | |
| postalCode | String | no | |
| country | String | yes | ISO code (3 letters) of the country |
| powerType | String | yes | Possible values: AC,DC,AC\_1\_PHASE,AC\_3\_PHASE |
| private | Boolean | yes | whether the location is private or not |
| coordinates | Object([GeoCoordinates](/docs/drive-api/cdr#geocoordinates)) | yes | the location's geo coordinates |

##### GeoCoordinates

| field | type | mandatory |
| :- | :- | :- |
| latitude | Double | yes |
| longitude | Double | yes |

##### Cost

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| totalCostMinorUnitsInclVat | Long | yes | total cost including VAT in [minor units](/docs/drive-api/cdr#minor-units) |
| totalCostMinorUnitsExclVat | Long | yes | total cost excluding VAT in [minor units](/docs/drive-api/cdr#minor-units) |
| currency | String | yes | Currency ISO code (3 letters) |
| priceIncludesVat | Boolean | yes | Wheter the price (per unit) is including VAT or not |
| costSegments | Array of [CostSegment](/docs/drive-api/cdr#costsegment) | yes | |

##### CostSegment

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| type | String / Enum | yes | Possible values: TIME, ENERGY, FLAT, CAPPED, PARKING\_TIME, CONGESTION\_TIME |
| quantity | Double | yes | amount of [unit](/docs/drive-api/cdr#unit) objects |
| vatRate | Double | yes | |
| pricePerUnit | Double | yes | price per unit honuring all decimals. The attribute `priceIncludesVat` tells if it is including or excluding VAT. |
| pricePerUnitMinorUnitsInclVat | Long | yes | price per unit in [minor units](/docs/drive-api/cdr#minor-units). Despite the name does not necessarily include VAT. The attribute `priceIncludesVat` will tell. |
| segmentCostMinorUnitsExclVat | Long | yes | segment cost excluding VAT in [minor units](/docs/drive-api/cdr#minor-units) |
| segmentCostMinorUnitsInclVat | Long | yes | segment cost including VAT in [minor units](/docs/drive-api/cdr#minor-units) |
| unit | Object [Unit](/docs/drive-api/cdr#unit) | yes | `min` or `h` for TIME, PARKING\_TIME, and CONGESTION\_TIME, `Wh` or `kWh` for ENERGY, `session` for FLAT and CAPPED |
| from | String ISO date format | no\* | `2022-12-12T12:55:21Z` always UTC \* - mandatory for TIME, PARKING\_TIME, CONGESTION\_TIME, and ENERGY types |
| to | String ISO date format | no\* | `2022-12-12T12:55:21Z` always UTC \* - mandatory for TIME, PARKING\_TIME, CONGESTION\_TIME, and ENERGY types |

##### Minor units

Minor units are the smallest subdivisions of each currency.<br />Here are the specifications for handling costs in various currencies:

| Currency Code | Currency Name | Minor Unit | Example Cost |
| - | - | - | - |
| EUR | Euro | Cents | `100` cents → €1.00 |
| SEK | Swedish Krona | Öre | `100` öre → 1 SEK |
| DKK | Danish Krone | Øre | `100` øre → 1 DKK |
| NOK | Norwegian Krone | Øre | `100` öre → 1 NOK |
| GBP | British Pound | Pence | `100` pence → £1.00 |

For more information, check [minor units explanation](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes).

##### HomeCharging

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| exemptVat | Boolean | yes | if true, this Home Charging session is exempt from VAT. |

##### Unit

Describes what `quantity` in PriceElement refers to. For ENERGY, FLAT, and CAPPED the Unit does not change, but for TIME, PARKING\_TIME, and CONGESTION\_TIME it can differ.

| field | type | mandatory | notes |
| :- | :- | :- | :- |
| type | String | yes | `session`, `kWh`, `Wh`, `min`, or `h` |
| amount | number(integer) | yes | Always `1` for `session`, `kWh`, and `Wh`. For time-based units, the amount represents the billed step size, for example `1` hour or `15` / `60` minutes. |

`segmentCostMinorUnitsInclVat` contains the total price for this segment.<br />It’s calculated as `quantity *  pricePerUnitMinorUnitsInclVat`.

In the example below, the user charged over one hour. With a step size of one hour (60 minutes), this results in a quantity of two ‘steps’. See [Prices](/docs/drive-api/geosearch#prices) for more details on how step size works.

Example PriceElement

```json theme={null}
{
  "type": "TIME",
  "quantity": 2,
  "vatRate": 25.00,
  "pricePerUnit": 126.00,
  "pricePerUnitMinorUnitsInclVat": 12600,
  "segmentCostMinorUnitsExclVat": 20160,
  "segmentCostMinorUnitsInclVat": 25200,
  "unit": {
    "type": "min",
    "amount": 60
  },
  "from": "2023-06-13T12:55:21Z",
  "to": "2023-06-13T14:22:33Z"
}
```

In the `priceElements` array the same type might show up multiple times (example: the price is 1 EUR/min from 00:00 to 12:00 and 0.5 EUR/min from 12:00 to 00:00, and the session lasted from 11:30 till 13:00. The CDR will contain two TIME cost segments).

### Examples

#### User model example

```json theme={null}
{
  "requestId": "ej4KDd2kKdj",
  "timestamp": "2024-09-25T13:44:27Z",
  "payer": {
    "id": "FJEDK34KSJ9WD",
    "chargingKey": {
      "uid": "NR9348593",
      "contractId": "DE-8PS-C1Z3R5KZI-8",
      "visualNumber": "2340-1254-6543-5655",
      "identificationType": "rfid",
      "type": "CHARGING_KEY"
    },
    "type": "USER"
  },
  "type": "SESSION",
  "item": {
    "sessionId": "BpK64y1z1QA",
    "externalSessionId": "89111162",
    "startTime": "2024-09-25T13:17:57Z",
    "stopTime": "2024-09-25T13:44:18Z",
    "empCost": {
      "totalCostMinorUnitsInclVat": 26605,
      "totalCostMinorUnitsExclVat": 21284,
      "currency": "NOK",
      "priceIncludesVat": true,
      "costSegments": [
        {
          "type": "ENERGY",
          "quantity": 44.416,
          "pricePerUnit": 5.99,
          "pricePerUnitMinorUnitsInclVat": 599,
          "segmentCostMinorUnitsExclVat": 21284,
          "segmentCostMinorUnitsInclVat": 26605,
          "unit": {
            "type": "kWh",
            "amount": 1
          },
          "vatRate": 25.0,
          "from": "2024-09-25T13:17:57Z",
          "to": "2024-09-25T13:44:18Z"
        }
      ]
    },
    "cpoClaimedCost": {
      "totalCostMinorUnitsInclVat": 31594,
      "totalCostMinorUnitsExclVat": 25275,
      "currency": "NOK",
      "priceIncludesVat": false,
      "costSegments": [
        {
          "type": "FLAT",
          "quantity": 1.0,
          "pricePerUnit": 252.75,
          "pricePerUnitMinorUnitsInclVat": 25275,
          "segmentCostMinorUnitsExclVat": 25275,
          "segmentCostMinorUnitsInclVat": 31594,
          "unit": {
            "type": "session",
            "amount": 1
          },
          "vatRate": 25.0
        }
      ]
    },
    "energyConsumedInWh": 44416,
    "totalParkingTimeInSeconds": 0,
    "location": {
      "id": "1ih2FfSRddmxioJEmsyDcv9a2h+e83m568C3LyoNkIs=",
      "evseId": "NO*CHA*E2496*A",
      "cpoId": "NO*REC",
      "address": "Hardangervegen 697",
      "city": "Haukeland",
      "postalCode": "5268",
      "country": "NOR",
      "powerType": "DC",
      "private": false
    },
    "startMethod": "REMOTE"
  }
}
```

#### Charging key model example

```json theme={null}
{
  "requestId": "fjei9pw0c",
  "timestamp": "2024-09-25T15:00:51Z",
  "payer": {
    "uid": "93042D4B7AD96280",
    "contractId": "GB-ALS-EF90LT13L-O",
    "visualNumber": "RFID34957483933",
    "identificationType": "rfid",
    "type": "CHARGING_KEY"
  },
  "type": "SESSION",
  "item": {
    "sessionId": "mezoOeWGdmpa",
    "externalSessionId": "GBKR40G9F92JDKS",
    "startTime": "2024-09-25T13:54:42Z",
    "stopTime": "2024-09-25T15:00:46Z",
    "empCost": {
      "totalCostMinorUnitsInclVat": 2380,
      "totalCostMinorUnitsExclVat": 1983,
      "currency": "GBP",
      "priceIncludesVat": true,
      "costSegments": [
        {
          "type": "ENERGY",
          "quantity": 30.131,
          "pricePerUnit": 0.79,
          "pricePerUnitMinorUnitsInclVat": 79,
          "segmentCostMinorUnitsExclVat": 1983,
          "segmentCostMinorUnitsInclVat": 2380,
          "unit": {
            "type": "kWh",
            "amount": 1
          },
          "vatRate": 20.0,
          "from": "2024-09-25T13:54:42Z",
          "to": "2024-09-25T15:00:41Z"
        }
      ]
    },
    "cpoClaimedCost": {
      "totalCostMinorUnitsInclVat": 2856,
      "totalCostMinorUnitsExclVat": 2380,
      "currency": "GBP",
      "priceIncludesVat": false,
      "costSegments": [
        {
          "type": "FLAT",
          "quantity": 1.0,
          "pricePerUnit": 23.8,
          "pricePerUnitMinorUnitsInclVat": 2380,
          "segmentCostMinorUnitsExclVat": 2380,
          "segmentCostMinorUnitsInclVat": 2856,
          "unit": {
            "type": "session",
            "amount": 1
          },
          "vatRate": 20.0
        }
      ]
    },
    "energyConsumedInWh": 30131,
    "totalParkingTimeInSeconds": 4,
    "location": {
      "id": "pdM9JM8qNtM3MX761qOGUlhljKJ/n6l1lfXMU2w0AivH5+3bUvPdMZyqaUV98hyfgaLCza2kwLfKutDcynXGpQ==",
      "evseId": "GB*OSP*EOSP20191*2",
      "cpoId": "GB*OSP",
      "address": "Weedon Bec",
      "city": "Northampton",
      "postalCode": "NN7 4QD",
      "country": "GBR",
      "powerType": "DC",
      "private": false
    },
    "startMethod": "LOCAL"
  }
}
```

## Security

If you want to be sure that CDRs you receive are emitted by Plugsurfing, please check the [Verify HMAC Signatures](/docs/drive-api/verifying-hmac-signature) guide.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.