Skip to main content
OCPI is symmetric. You are a server to your roaming partners and a client to them, often in the same request. EVPanda records both sides. Shared setup (install, API key, config, shutdown, troubleshooting) is in Getting started.

Model

The direction follows from the method you call, so there is no argument to get backwards.
Identity is always the partner on the other side, in both directions, never your own platform.

Adapters

The adapters assemble the exchange for you: collect headers and bodies, resolve the partner, call the right method. A request with no resolvable identity is served exactly as it would have been, just not captured.

Telling the adapters who the partner is

Your OCPI server already knows: you looked the partner up by their Token A or Token B to decide whether to serve the request. Stamp the identity there. Each SDK reads three carriers in order, and falls back to the X-EVPanda-* headers last.
Stamp the request context in the middleware that authenticates the partner.
Outbound, stamp the context on the request you are about to send:
Mount order matters in Go. The middleware resolves identity before it calls the next handler, so your auth layer has to run outside it: auth(ocpi.Middleware(panda)(mux)). Wrapped the other way around, no request has an identity yet and nothing is captured.

The X-EVPanda headers

If nothing else carries the identity, every adapter reads these, matched case insensitively. Partners never send them and never receive them: the outbound adapters strip all four before dispatch.

Custom resolvers

If identity lives somewhere else entirely, a client certificate or a path prefix, pass your own resolver. Returning nothing means the exchange is not captured, and the request is never blocked or altered.

Complete example

server.go
Other routers, since the middleware is the stdlib shape:

Per-SDK notes

The middleware resolves identity up front, before the handler runs, so the auth layer that stamps the context has to be outside it. Node and Python read their carrier when the response finishes, which is why mount order is free there.
The express adapter reads the request body from req.body rather than teeing the raw stream, since a data listener would flip the stream to flowing mode and could starve your own parser. Without a parser, exchanges are captured with no request body.express.raw() gives the exact bytes off the wire. express.json() gives a re-serialized form, which is usually fine and occasionally differs in key order or whitespace.
In Node, axios goes through node:http and never fetch. Wrapping one captures nothing from the other. Instrument whichever client your OCPI calls actually use, or both.
WSGI gives no way to observe a body the handler never reads, so the WSGI middleware takes it at the start of the request. That is a copy per in-flight request, bounded by max_capture_bytes. The ASGI adapter does not pay it.
Outbound capture completes when the response body is read to the end or closed. A leaked body is a message that never ships. A call that fails at the transport layer captures nothing, because there was no exchange.

Capturing without an adapter

When no adapter fits, build the exchange yourself.
captureOutboundMessage is the same call for the other direction. Both are non-blocking and never raise. In Go, use SetRequestBody and SetResponseBody rather than assigning the fields: they copy, so you can reuse a pooled buffer as soon as the call returns.
Always set a status code. The SDKs let you omit it, but the ingestion API requires a value between 100 and 599 and rejects the message without one, after a successful delivery. For an exchange that never produced a response, skip the capture instead.

Capture extra headers

OCPI headers pass through an allowlist, so Authorization and everything else unlisted is dropped at capture. ocpiAllowedHeaders extends the list and can never shrink it.
See Data flow for the defaults.

Common mistakes

Next

Data flow

Exchange shape, the header allowlist, credentials masking.

Getting started

Config, stats, shutdown, troubleshooting.