Configuration
This page explains the runtime files that matter most when you deploy the public OPI Proxy bundle.
Quick Links
The Main Runtime Files
The public bundle uses one .env file plus a small set of JSON files in config/:
.envconfig/terminal-models.jsonconfig/terminal-profiles.jsonconfig/terminals.jsonconfig/auth.json
The .env File
The most important .env settings are:
HOSTPUBLISHED_HOSTPORTTLS_MODETLS_CERT_FILETLS_KEY_FILELOG_LEVELSTORAGEDB_URLRESPONSE_MODEASYNC_ACCEPT_GRACE_MS
Good starting points:
- local-only testing:
PUBLISHED_HOST=127.0.0.1 - shared network access:
PUBLISHED_HOST=0.0.0.0 - non-local access:
TLS_MODE=required
Storage And Response Mode
By default, the proxy stores transactions and related runtime data in a database. SQLite is used for runtime transactions, receipts, terminal states, and proxy locks. Terminal configuration, terminal profiles, terminal models, authentication configuration, logs, and certificates remain file based.
STORAGE=db
DB_URL=file:/app/data/opi-proxy.sqliteFor testing purposes, you may use STORAGE=json. In this mode, transactions and receipts are stored as JSON files under data/.
STORAGE=jsonBy default, the proxy uses asynchronous responses. With RESPONSE_MODE=async, the proxy returns 202 Accepted once the operation has been accepted and persisted.
RESPONSE_MODE=asyncFor payment-style operations, the async response includes a transactionId. The proxy also waits briefly for immediate terminal rejections before returning 202; this is controlled by ASYNC_ACCEPT_GRACE_MS, default 1000. If the terminal answers immediately with a final failed state, the API returns an error instead of exposing a new pending transaction. Fresh terminal-side states such as DeviceUnavailable or PrintLastTicket are also cached briefly, controlled by RETRY_AFTER_DEVICE_UNAVAILABLE_MS; during that window, new operations return 503 with Retry-After instead of creating another pending transaction. For service-style operations, such as /config, /init, /transmit, /close, /reprint, /abort, and /reset, the proxy returns an empty HTTP 202 with Preference-Applied: respond-async. RESPONSE_MODE=sync makes the proxy wait until the terminal operation has finished and returns the final result. This can be useful for testing transaction flows where the integrator wants to wait for the result returned by the final message of the terminal communication.
RESPONSE_MODE=syncIntegrators may override the configured response mode per request by sending:
{
"responseMode": "async"
}Or by using the HTTP Prefer header:
Prefer: respond-asyncMutating routes also support the Idempotency-Key header. Reusing the same key with the same request method, route, async preference, and JSON body returns the same proxy response again. Reusing the same key with different request content returns HTTP 409.
Terminal Models, Profiles, and Terminals
The effective terminal config is merged in this order:
- request values
- terminal-specific values from
terminals.json - profile values from
terminal-profiles.json - model defaults from
terminal-models.json - global defaults from
.env
workstationId is the POS/ECR identity sent to the terminal as OPI WorkstationID. Use a stable, unique value for each checkout or terminal context, for example lane-1, front-counter, or mobile-dx. If workstationId is omitted, the proxy uses the terminal alias as the fallback.
Receipt Handling
The JSON API uses a cleaner receipt abstraction than raw OPI printer status fields.
Example:
{
"receiptHandling": {
"customer": "external",
"merchant": "external"
}
}Allowed values:
externallocalunavailable
The proxy maps that model to the required OPI printer status fields internally:
externalmeans the ECR/proxy is available for receipt output. The terminal sends receipt copies over the device callback channel and the proxy stores them underdata/receipts.localmeans the terminal handles that receipt copy locally, for example with its built-in printer.unavailablemeans no printer or receipt destination is available for that copy.
On standalone terminals used in ECR/OPI mode, receipt behaviour can differ from standalone terminal operation. With local printing, a terminal may send the cardholder receipt question over the device callback channel; the proxy answers yes by default so the payment context can finalize. Use external when receipts should be captured by the proxy under data/receipts instead of printed locally. In external mode, the ECR is responsible for asking whether the customer receipt should be printed, shown, sent, or skipped.
If ports are omitted from terminals.json, the proxy uses the model defaults, normally payChannel0=4100 and deviceChannel1=4102. Multiple terminals may use the same callback port; the proxy keeps one shared listener per host/port and routes device callbacks by WorkstationID. Keep workstationId unique for terminals that share a callback port.
For real terminal traffic, remember that two OPI paths are involved:
- the proxy connects to the terminal on
payChannel0, normally TCP4100 - the terminal connects back to the proxy on
deviceChannel1, normally TCP4102
The Docker host must publish the callback port with DEVICE_CHANNEL1_PUBLIC_PORT, and the terminal must be able to reach that host IP and port on the local network. If the callback path is blocked, the payment request can reach the terminal while receipt, display, journal, or input callbacks fail.
Payment Method Filtering
Requests can optionally include paymentMethods to restrict which explicitly filterable brands are allowed for processing.
Example:
{
"paymentMethods": ["visa", "mastercard", "twint"]
}Important:
- this controls processing only
- it does not change which brands are shown on the terminal screen
- the terminal display is still determined by terminal configuration
- the example values reflect brands officially tested through the OPI Proxy as of today
- other brands relevant to the Swiss market can still work through OPI even if they are not yet available for request-level filtering through
paymentMethods
Ownership Model
Treat the files like this:
terminal-models.json: Nexi-owned release catalog dataterminal-profiles.json: merchant or integrator owned after first setupterminals.json: merchant or integrator owned after first setupauth.json: generated locally and always environment owned