Configuration

This page is currently only available in English.

This page explains the runtime files that matter most when you deploy the public OPI Proxy bundle.

The Main Runtime Files

The public bundle uses one .env file plus a small set of JSON files in config/:

  • .env
  • config/terminal-models.json
  • config/terminal-profiles.json
  • config/terminals.json
  • config/auth.json

The .env File

The most important .env settings are:

  • HOST
  • PUBLISHED_HOST
  • PORT
  • ALLOW_INSECURE_REMOTE_HTTP
  • TLS_MODE
  • TLS_CERT_FILE
  • TLS_KEY_FILE
  • LOG_LEVEL
  • STORAGE
  • DB_URL
  • RESPONSE_MODE
  • ASYNC_ACCEPT_GRACE_MS
  • RETRY_AFTER_DEVICE_UNAVAILABLE_MS
  • REQUEST_TIMEOUT_MS
  • REQUEST_MAX_DURATION_MS

Good starting points:

  • initial testing on a trusted network: HOST=0.0.0.0, TLS_MODE=disabled, and ALLOW_INSECURE_REMOTE_HTTP=true
  • shared or remotely reachable access: TLS_MODE=required and ALLOW_INSECURE_REMOTE_HTTP=false

Networking and Timeouts

The public Linux bundle uses Docker host networking. Normally, the proxy sends OPI requests to the terminal on payChannel0=4100 and listens for callbacks on deviceChannel1=4102. Legacy Compose variables such as PUBLISHED_HOST and DEVICE_CHANNEL1_PUBLIC_PORT are ignored while network_mode: host is active; the actual callback port comes from the effective terminal configuration.

The main timeout controls are:

Variable Public default Purpose
OPI_CONNECT_TIMEOUT_MS 3000 Maximum time to connect to a terminal.
REQUEST_TIMEOUT_MS 90000 Idle timeout while waiting for OPI data or callbacks; activity refreshes it.
REQUEST_MAX_DURATION_MS 300000 Hard maximum for an OPI operation even while callbacks continue.
ASYNC_ACCEPT_GRACE_MS 1000 Short wait for an immediate terminal failure before returning 202.
RETRY_AFTER_DEVICE_UNAVAILABLE_MS 5000 Cooldown after a fresh DeviceUnavailable or PrintLastTicket.

Keep upstream HTTP and reverse-proxy timeouts longer than the expected cardholder interaction when using synchronous mode.

Storage And Response Mode

By default, the proxy stores transactions and related runtime data in a database. SQLite is used for runtime transactions, receipt bodies, terminal states, and proxy locks. Receipt records are written to the SQLite database selected by DB_URL; they are not additionally written as files under data/receipts. Terminal configuration, terminal profiles, terminal models, authentication configuration, logs, and certificates remain file based.

STORAGE=db
DB_URL=file:/app/data/opi-proxy.sqlite

For testing purposes, you may use STORAGE=json. In this mode, transactions are stored under data/ and receipt bodies are written as files under data/receipts.

STORAGE=json

By 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=async

For 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=sync

Integrators may override the configured response mode per request by sending:

{
  "responseMode": "async"
}

Or by using the HTTP Prefer header:

Prefer: respond-async

Mutating 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 officially supported model catalog covers Ingenico RX5000, DX8000, and EX8000, plus PAX Q30, A77, IM15, and IM30.

The effective terminal config is merged in this order:

  1. request values
  2. terminal-specific values from terminals.json
  3. profile values from terminal-profiles.json
  4. model defaults from terminal-models.json
  5. 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

Receipt routing can be configured per terminal in terminals.json and overridden per payment request. Both use a cleaner abstraction than raw OPI printer status fields.

Per-terminal example:

{
  "dx-prod": {
    "terminalModel": "DX8000",
    "terminalProfile": "dx8000-default",
    "ip": "192.168.50.33",
    "enabled": true,
    "receiptHandling": {
      "customer": "external",
      "merchant": "external"
    }
  }
}

Allowed values:

  • external
  • local
  • unavailable

The proxy maps that model to the required OPI printer status fields internally:

  • external means the ECR/proxy is available for receipt output. The terminal sends receipt copies over the device callback channel and the proxy stores them using the configured storage backend.
  • local means the terminal handles that receipt copy locally, for example with its built-in printer.
  • unavailable means 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 instead of printed locally. With STORAGE=db, receipt bodies are stored in SQLite; with STORAGE=json, they are stored under data/receipts. The same receipt API routes are used with either backend. 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 TCP 4100
  • the terminal connects back to the proxy on deviceChannel1, normally TCP 4102

With the supplied host-networked Linux deployment, the proxy listens directly on the configured deviceChannel1 port; DEVICE_CHANNEL1_PUBLIC_PORT is a legacy bridge-network setting and does not publish or remap it. The terminal must be able to reach the Docker host IP and callback 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 data
  • terminal-profiles.json: merchant or integrator owned after first setup
  • terminals.json: merchant or integrator owned after first setup
  • auth.json: generated locally and always environment owned