Configuration Properties - phax/phoss-ap GitHub Wiki
All properties are located in the file application.properties which resides in src/main/resources of the phoss-ap-webapp module.
All configuration properties are resolved according to the rules described in https://github.com/phax/ph-commons/wiki/ph-config
Many time-related properties accept a duration grammar since v0.9.0. The format is one or more [sign]<number><unit> segments, summed together, with optional whitespace between segments. Supported units:
| Suffix | Meaning |
|---|---|
ns |
nanoseconds |
us |
microseconds |
ms |
milliseconds |
s |
seconds |
m |
minutes |
h |
hours |
d |
days |
Examples: 5s, 1m 30s, 2d 12h, 90d.
The previous millisecond-typed keys (with .ms / .millis suffix) continue to be honored for backwards compatibility, but a deprecation warning is logged (once per key) when they are used. The legacy .ms / .millis keys will be removed in v1.0.0. Migrate to the new duration-grammar keys as soon as possible.
Table of Contents
- General Configuration
- Peppol Configuration
- AS4 Keystore and Truststore
- phase4 / AS4 Internals
- SMP Client Configuration
- Database Configuration
- API Configuration
- Document Storage
- Outbound S3 Submission
- Sending Configuration
- Receiving Configuration
- Forwarding Configuration
- Retry Configuration
- Verification Configuration
- MLS Configuration
- Reporting Configuration
- Duplicate Detection Configuration
- Archival Configuration
- Cleanup Configuration
- Directory Sender Configuration
- Notification Configuration
- HTTP Proxy Configuration
- Shutdown and Startup Recovery
- Management Status Endpoint
- Logging and Diagnostics
- Sentry Error Tracking
- OpenTelemetry
- Spring Boot / Server Configuration
-
global.debug: Enable or disable overall debug mode. Enables additional checks that should not be active in production (e.g. verbose logging, slower validation). Default isfalse. -
global.production: Enable or disable overall production mode. Whenfalse, certain production-only functionality may be disabled. Default isfalse. -
global.nostartupinfo: Set totrueto suppress the startup information banner. Default isfalse. -
global.datapath: The path where all relevant data files are stored (e.g. keystores, dumps, logs). Should be an absolute path in production (e.g./var/phoss-ap/data). The official Docker image sets this to/var/phoss-ap/data/since 0.12.0 - see Running phoss AP.
-
peppol.stage: The Peppol network stage. Eithertestorprod. Determines which PKI certificates and SML endpoints are used. -
peppol.owner.seatid: The Peppol Seat ID of this AP (e.g.POP000000). Used in reporting and MLS processing. -
peppol.owner.countrycode: The 2-letter country code of the AP operator. Used as C2 or C3 country in reporting context. -
peppol.receiver-check.mode(since v0.1.3): Controls how incoming messages are verified against SMP registrations. Possible values:-
none(default) — Receiver checks are disabled. -
smp— Uses a fixed SMP URL for verification. Requirespeppol.smp.urlandphase4.endpoint.addressto be set. -
sml— Uses dynamic per-participant SMP resolution via the SML derived from the configuredpeppol.stage. Requiresphase4.endpoint.addressto be set.
-
-
peppol.smp.url: The URL of this AP's own SMP (e.g.http://localhost:8080). Required whenpeppol.receiver-check.mode=smp. -
peppol.dns.servers(since v0.1.3): Comma-separated list of DNS server hostnames or IP addresses to use for Peppol NAPTR lookups (e.g.8.8.8.8,8.8.4.4). Optional. When not set, the system default DNS resolver is used. -
peppol.identifier.mode: The Peppol identifier validation mode. Eitherstrictorlax. Instrictmode (default), all Peppol identifier rules are enforced. Inlaxmode, identifier validation is more lenient. Default isstrict. -
peppol.revocation.soft-fail(since 0.9.0): Global soft-fail mode for certificate revocation checks. Whentrue, certificate validation succeeds even if the revocation status cannot be determined (e.g. CRL/OCSP responder unreachable due to a network error). Maps toCertificateRevocationCheckerDefaults.setAllowSoftFail(boolean). Default isfalse(hard fail on indeterminate revocation status).
Outbound SMP lookups (Service Group and Service Metadata queries) are cached in memory and shared between all SMP clients of the process, so that sending many messages to the same receiver only triggers one SMP query per cache period. Only successful responses are cached - failures are always retried. All SMP clients additionally share a single HTTP connection pool.
-
peppol.smp.cache.enabled(since 0.11.0): Enable or disable the in-memory caching of SMP responses. Default istrue. Set tofalseto query the SMP for every single message (e.g. while testing SMP registration changes). -
peppol.smp.cache.ttl(since 0.11.0): Duration after which a cached SMP response is considered expired and is queried again. Default is15m. A shorter value picks up SMP changes faster, a longer value reduces the load on the SMP. -
peppol.smp.cache.max-size(since 0.11.0): Maximum number of entries per cache (one cache for Service Groups and one for Service Metadata), evicting the least recently used entries first. Values ≤ 0 mean no limit. Default is1000. -
peppol.smp.timeout.connect(since 0.13.0): Connect timeout for all SMP queries. Default is5s, which is the value that was hardcoded before 0.13.0. -
peppol.smp.timeout.response(since 0.13.0): Response (read) timeout for all SMP queries. Default is10s, which is the value that was hardcoded before 0.13.0. Raising it makes the SMP circuit breaker less likely to open because of a slow SMP, at the price of a slower outbound pipeline.
Note: inbound receiver checks with peppol.receiver-check.mode=sml are performed inside phase4 and are not covered by this cache.
These properties configure the cryptographic material used by phase4 for AS4 message signing, encryption, and certificate validation.
-
org.apache.wss4j.crypto.merlin.keystore.type: Keystore type. EitherJKSorPKCS12. Default isJKS. -
org.apache.wss4j.crypto.merlin.keystore.file: Path to the keystore file containing the AP certificate and private key. -
org.apache.wss4j.crypto.merlin.keystore.password: Password for the keystore. -
org.apache.wss4j.crypto.merlin.keystore.alias: Alias of the key entry within the keystore. -
org.apache.wss4j.crypto.merlin.keystore.private.password: Password for the private key entry.
-
org.apache.wss4j.crypto.merlin.truststore.type: Truststore type. EitherJKSorPKCS12. Default isPKCS12. -
org.apache.wss4j.crypto.merlin.truststore.file: Path to the truststore file for validating incoming AS4 message signatures. -
org.apache.wss4j.crypto.merlin.truststore.password: Password for the truststore.
These properties are read directly by the embedded phase4 library and control its internal behavior. They are defined in com.helger.phase4.config.AS4Configuration. Defaults are normally appropriate for a Peppol AP — change them only if you understand the consequences.
-
phase4.manager.inmemory: Whether phase4's internal state managers (e.g. the incoming message ID duplicate cache) use the in-memory implementation. Default istrue. phoss-ap operates with the in-memory phase4 managers; do not set this tofalseunless you have provided an alternative manager backend. -
phase4.wss4j.syncsecurity: Force WSS4J sign/verify and encrypt/decrypt operations to be globally synchronized. Required when multiple worker threads send or receive AS4 messages concurrently within the same JVM and the underlying WSS4J/Santuario stack would otherwise be unsafe. Default isfalse. -
phase4.default.profile(with fallbackphase4.profile): The AS4 profile identifier used by phase4 when no profile is selected explicitly. For phoss-ap (which is a Peppol AP) the Peppol profile is selected programmatically, so this rarely needs to be set. Optional. -
phase4.errormsg.include.stacktraces: Whether AS4 error signal messages returned to remote senders include Java stack traces. Default isfalse. Keep disabled in production — stack traces may leak internal details. -
phase4.compatibility.domibus: Enable special Domibus interoperability tweaks. Default isfalse. Not applicable to standard Peppol exchanges. -
phase4.http.response.accept.allstatuscodes: Whether phase4 accepts all HTTP response status codes from the remote AS4 endpoint (instead of rejecting status codes >= 300). Default istrue(the behavior introduced in phase4 4.1.1). Set tofalseto restore the pre-4.1.1 behavior where only status codes < 300 were accepted.
See also phase4.endpoint.address and phase4.api.requiredtoken under API Configuration, phase4.dump.path and phase4.dump.mode under Logging and Diagnostics, and phase4.incoming.duplicatedisposal under Duplicate Detection Configuration.
These properties configure the SMP client used for outbound SMP lookups (to resolve receiver AP endpoints).
-
smpclient.truststore.type: Truststore type for SMP client TLS connections. EitherJKSorPKCS12. Default isPKCS12. -
smpclient.truststore.path: Path to the truststore file for SMP client connections. -
smpclient.truststore.password: Password for the SMP client truststore.
PostgreSQL and MySQL are the supported databases. The database schema is managed automatically via Flyway.
JDBC configuration properties are defined by JdbcConfigurationConfig from ph-db. All properties use the prefix phossap.jdbc..
-
phossap.jdbc.database-type: The target database type. EitherPostgreSQLorMySQL. -
phossap.jdbc.driver: The JDBC driver class. For PostgreSQL useorg.postgresql.Driver. For MySQL usecom.mysql.cj.jdbc.Driver. -
phossap.jdbc.url: The JDBC URL of the database. Example for PostgreSQL:jdbc:postgresql://localhost:5432/phossap. Example for MySQL:jdbc:mysql://localhost:3306/phoss-ap?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false. It's recommended to not use theserverTimezoneparameter. -
phossap.jdbc.user: The database user. -
phossap.jdbc.password: The password of the database user. -
phossap.jdbc.schema: The database schema to use. For PostgreSQL defaults topublic. Not used for MySQL (the database name in the JDBC URL serves this purpose). -
phossap.jdbc.execution-time-warning.enabled: Enable or disable logging of long-running JDBC transactions. Default istrue. -
phossap.jdbc.execution-time-warning.ms: Threshold in milliseconds for the long-running JDBC transaction warning. Default is1000. -
phossap.jdbc.debug.connections: Enable or disable debug logging of JDBC connection lifecycle (open/close). Default isfalse. -
phossap.jdbc.debug.transactions: Enable or disable debug logging of JDBC transactions (begin/commit/rollback). Default isfalse. -
phossap.jdbc.debug.sql: Enable or disable debug logging of executed SQL statements. Default isfalse.
-
phossap.jdbc.pooling.max-connections: Maximum number of active connections in the pool. Default is8. -
phossap.jdbc.pooling.max-wait(since 0.9.0): Maximum time to wait for a connection from the pool before throwing an exception. Default is10s. -
phossap.jdbc.pooling.max-wait.millis(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is10000. -
phossap.jdbc.pooling.between-evictions-runs(since 0.9.0): Interval between runs of the idle connection evictor. Default is5m. -
phossap.jdbc.pooling.between-evictions-runs.millis(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is300000. -
phossap.jdbc.pooling.min-evictable-idle(since 0.9.0): Minimum idle time before a connection is eligible for eviction. Default is30m. -
phossap.jdbc.pooling.min-evictable-idle.millis(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is1800000. -
phossap.jdbc.pooling.remove-abandoned-timeout(since 0.9.0): Timeout before an abandoned connection can be removed. Default is5m. -
phossap.jdbc.pooling.remove-abandoned-timeout.millis(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is300000.
Configuration properties for Flyway database migrations, as defined by FlywayConfigurationBuilderConfig from ph-db. All properties use the prefix flyway. and fall back to the main JDBC properties if not explicitly set.
-
phossap.flyway.enabled: Enable or disable Flyway database migrations on startup. Default istrue. -
phossap.flyway.jdbc.url: Optional separate JDBC URL for Flyway migrations. Falls back tophossap.jdbc.urlif not set. -
phossap.flyway.jdbc.user: Optional separate database user for Flyway. Falls back tophossap.jdbc.userif not set. -
phossap.flyway.jdbc.password: Optional separate database password for Flyway. Falls back tophossap.jdbc.passwordif not set. -
phossap.flyway.jdbc.schema-create: Whether Flyway should attempt to create the database schema. Default isfalse. -
phossap.flyway.baseline.version: The Flyway baseline version (integer). Used when baselining an existing database. Default is0. -
phossap.flyway.history-table(since 0.2.2): Custom name for the Flyway schema history table. If not set, the Flyway default (flyway_schema_history) is used. -
phossap.flyway.debug-mode(since 0.2.2): Enable Flyway debug logging during migration execution. Default isfalse. -
phossap.flyway.repair-mode(since 0.2.2): Run Flyway'srepair()before executing migrations to fix inconsistencies in the schema history table. Default isfalse.
The peppol-reporting library uses its own database for storing Peppol Reporting items (TSR/EUSR). It defines JDBC properties under the prefix peppol.reporting.jdbc. and Flyway properties under peppol.reporting.flyway.. See the peppol-reporting SQL backend configuration for the full list of properties.
When sharing the same database as phoss-ap, set a custom Flyway history table to avoid conflicts:
-
peppol.reporting.flyway.history-table: Custom name for the Flyway schema history table used by peppol-reporting. If not set, the Flyway default (flyway_schema_history) is used — which would conflict with the phoss-ap history table when using the same database. Example:flyway_schema_history_reporting.
The peppol-ap-support library uses its own database for tracking Peppol Report sending state. It defines JDBC properties under the prefix peppol.report.jdbc. and Flyway properties under peppol.report.flyway.. See the peppol-ap-support SQL backend configuration for the full list of properties.
When sharing the same database as phoss-ap, set a custom Flyway history table to avoid conflicts:
-
peppol.report.flyway.history-table: Custom name for the Flyway schema history table used by peppol-ap-support. If not set, the Flyway default (flyway_schema_history) is used — which would conflict with the phoss-ap history table when using the same database. Example:flyway_schema_history_report.
-
phase4.endpoint.address: The public AS4 endpoint URL of this AP (e.g.https://ap.example.com/as4). Used by phase4 for endpoint comparison and in reporting. -
phase4.api.requiredtoken: The API token required for authenticating REST API calls (sent viaX-Tokenheader). If empty or not set, API authentication is disabled.
Document payloads (SBD files) are stored outside the database. The storage backend is selected via storage.mode — the local filesystem (default), Amazon S3, or a custom backend provided via SPI.
-
storage.mode: The document storage backend. One offilesystem(default),s3, orspi. When set tos3, documents are stored in an S3 bucket and their paths are recorded ass3://bucket/keyURIs in the database. When set tofilesystem, documents are stored as flat files on disk. When set tospi(since 0.10.4), documents are stored via a deployment-providedIDocumentPayloadManagerProviderSPIimplementation (see Runtime Extensions). -
storage.spi.id: (Required whenstorage.mode=spi, since 0.10.4) The ID of theIDocumentPayloadManagerProviderSPIprovider to use, matched against the provider'sgetID(). Startup fails if unset or if no provider with that ID is on the classpath. -
storage.inbound.path: Base path for inbound document storage. Used by thefilesystemands3modes — as a filesystem directory whenstorage.mode=filesystem, or as a key path component whenstorage.mode=s3. Default is${user.home}/phoss-ap/inbound. -
storage.outbound.path: Base path for outbound document storage. Used by thefilesystemands3modes — as a filesystem directory whenstorage.mode=filesystem, or as a key path component whenstorage.mode=s3. Default is${user.home}/phoss-ap/outbound.
These properties configure the S3 bucket used as the document storage backend when storage.mode=s3. All document payloads (both inbound and outbound) are stored in this bucket. Document paths in the database are recorded as s3://bucket/key URIs.
-
storage.s3.bucket: (Required whenstorage.mode=s3) The S3 bucket name for document storage. -
storage.s3.region: (Required whenstorage.mode=s3) The AWS region of the S3 bucket (e.g.eu-central-1). -
storage.s3.access-key-id: AWS access key ID for S3 access. Optional — if not set, the default AWS credential chain is used (IAM roles, environment variables, etc.). -
storage.s3.secret-access-key: AWS secret access key for S3 access. Optional — if not set, the default AWS credential chain is used. -
storage.s3.endpoint(since 0.2.2): Custom S3 endpoint URL for S3-compatible providers (e.g. MinIO, Garage). Optional — if not set, standard AWS S3 is used. -
storage.s3.path-style-access(since 0.2.2): Enable S3 path-style access instead of virtual-hosted style. Required by most S3-compatible providers. Default isfalse.
Note: The S3 storage backend is independent of the S3 forwarding mode (forwarding.mode=s3_link). S3 storage affects where the AP persists document payloads internally, while S3 forwarding controls how received documents are delivered to the Receiver Backend. Both can be used simultaneously.
These properties configure the AP's ability to fetch outbound documents from an S3 bucket provided by the Sender Backend, instead of receiving the document payload inline via the HTTP request body.
When enabled, the Sender Backend uploads the document to its own S3 bucket and then calls the POST /api/outbound/submit-s3 endpoint with the S3 bucket/key reference plus Peppol metadata. The AP fetches the document from S3 and processes it through the normal outbound pipeline.
-
outbound.s3.enabled: Enable or disable the/api/outbound/submit-s3endpoint. Default isfalse. -
outbound.s3.bucket: Default S3 bucket from which the AP fetches sender-uploaded documents. Can be overridden per request in the JSON body. Optional if always specified in the request. -
outbound.s3.region: (Required whenoutbound.s3.enabled=true) The AWS region of the sender's S3 bucket (e.g.eu-central-1). -
outbound.s3.access-key-id: AWS access key ID for accessing the sender's S3 bucket. Optional — if not set, the default AWS credential chain is used. -
outbound.s3.secret-access-key: AWS secret access key for accessing the sender's S3 bucket. Optional — if not set, the default AWS credential chain is used. -
outbound.s3.endpoint(since 0.2.2): Custom S3 endpoint URL for S3-compatible providers (e.g. MinIO, Garage). Optional — if not set, standard AWS S3 is used. -
outbound.s3.path-style-access(since 0.2.2): Enable S3 path-style access instead of virtual-hosted style. Required by most S3-compatible providers. Default isfalse.
Note: The sender's S3 bucket (configured here) may differ from the AP's storage S3 bucket (configured under storage.s3.*). The outbound S3 submission endpoint fetches from the sender's bucket and stores through the AP's own IDocumentPayloadManager (which may itself be S3-backed or filesystem-backed).
-
peppol.sending.enabled: Enable or disable outbound AS4 sending. Set tofalseto operate as a receive-only AP. Default istrue. -
outbound.dev-loopback.enabled(since v0.10.2): Development/testing aid. When set totrueandpeppol.stage=test, outbound sending bypasses the SMP/SML lookup and sends the generated AS4 message back to this AP's own AS4 endpoint (phase4.endpoint.address), using the AP's own AS4 certificate as the receiver certificate. This enables local end-to-end testing without SMP/SML registration or a public HTTPS endpoint for the test participant. Requiresphase4.endpoint.addressto be set. Only effective on the Peppol test stage — it is treated as disabled on the production stage regardless of this flag. Default isfalse.
-
peppol.receiving.enabled: Enable or disable inbound AS4 receiving. Set tofalseto operate as a send-only AP. Default istrue.
-
forwarding.mode: (Required) Selects the active forwarding implementation. One of:http_post_sync,http_post_async,s3_link,sftp,filesystem,spi. Must be explicitly configured — there is no default. -
forwarding.c4countrycode.modes(since v0.1.3): Comma-separated list of modes to try (in order) for automatically determining the C4 country code after successful inbound forwarding. Available modes:receiver_pid(derive from receiver participant ID scheme),business_card(look up from Peppol Directory Business Card via SMP). The asynchronous reporting API (POST /api/inbound/report) always remains available as an additional fallback. Default is empty (no automatic determination). Example:receiver_pid,business_card.
The three forwarding modes that store a document - sftp, filesystem and s3_link - name it according to a configurable pattern, so that a downstream routing service can route by e.g. the receiver participant ID without opening the payload or reading the metadata sidecar. The resolved value is the base name: the .xml of the document and the .json of the optional metadata sidecar are appended to it.
| Property | Default | Result |
|---|---|---|
forwarding.sftp.filename-pattern |
{datetime}_{incoming-id} |
The filename in forwarding.sftp.uploaddir
|
forwarding.filesystem.filename-pattern |
{sbdh-instance-id} |
The filename, resp. the subdirectory name in the per-transaction layout, in forwarding.filesystem.directory
|
forwarding.s3.filename-pattern |
{sbdh-instance-id} |
The object key, appended to forwarding.s3.key-prefix
|
Each default is exactly the layout of all versions before v0.12.0, so nothing changes unless the property is set. The same keys exist for a secondary forwarder (forwarding.secondary.{n}.…) and for the MLS copy sink (forwarding.mls-copy.…).
The supported placeholders are written as {name}. The ${name} syntax is not supported and is rejected at startup: ${...} is the variable syntax of ph-config itself (${otherKey}, or ${otherKey:default}), so such a value is replaced - by default with unresolved-var(name) - long before the forwarder sees it.
| Placeholder | Value |
|---|---|
{datetime} |
Reception timestamp, formatted as yyyyMMddHHmmss
|
{incoming-id} |
The phase4 Incoming ID; the transaction ID for a self-generated MLS copy |
{sbdh-instance-id} |
The SBDH Instance Identifier |
{receiver-id} |
The receiver participant ID including the identifier scheme, e.g. iso6523-actorid-upis__0151_35747532810
|
{receiver-value} |
The receiver participant ID without the identifier scheme, so everything after the ::, e.g. 0151_35747532810. The ICD stays part of the value, because the same number in a different ICD is a different participant |
{sender-id} |
The sender participant ID including the identifier scheme |
{sender-value} |
The sender participant ID without the identifier scheme |
{doctype-id} |
The Document Type ID - note that this one alone is longer than 150 characters |
{process-id} |
The Process ID |
Sanitization. Every character of the resolved name that is not a letter, a digit, ., - or _ is replaced with _, and leading dots are removed. That is deliberately stricter than the general filename sanitization: it is independent of the operating system the AP runs on - : is illegal on Windows only - and it makes sure that a value taken from the received document, like the sender provided SBDH Instance Identifier, can never point outside of the target directory. A resolved name longer than 200 characters (900 for an S3 object key) is truncated. Only for s3_link the literal part of the pattern may contain /, so that an object key can span "folders" - e.g. {receiver-value}/{datetime}_{incoming-id}; a value never contributes a /.
Validation. The pattern is checked at startup: an empty pattern, an unknown placeholder, unbalanced braces, a ${name} and - except for s3_link - a path separator abort the startup with a configuration error. A pattern that contains neither {incoming-id} nor {sbdh-instance-id} is accepted, but logs a warning: such a pattern cannot guarantee unique names, and the SFTP upload and the S3 upload silently overwrite an existing file resp. object of the same name. The filesystem forwarder is the only one that resolves a collision by itself, by appending a numeric suffix.
-
forwarding.spi.id: (Required whenforwarding.mode=spi) Stable ID of a deployment-providedIDocumentForwarderProviderSPI. The provider must be on the runtime classpath and registered throughMETA-INF/services/com.helger.phoss.ap.api.spi.IDocumentForwarderProviderSPI. After the provider creates the forwarder, the AP callsinitFromConfiguration(config, "forwarding."), so the SPI-provided forwarder may read any additionalforwarding.*keys it needs.
Whether the SPI-provided forwarder participates in synchronous delivery confirmation (and therefore yields an AP vs AB MLS response — see Forwarding Process) is determined by the IDocumentForwarder#isWithDeliveryConfirmation() value returned by the provider's forwarder instance.
Example:
forwarding.mode=spi
forwarding.spi.id=docnut
forwarding.endpoint=https://backend.example.com/inbound-
forwarding.http.endpoint: The URL of the Receiver Backend endpoint to POST the SBD to.
Every forwarding request carries these headers, independent of the custom ones below:
-
X-SBDH-Instance-ID: The SBDH Instance Identifier, so the Receiver Backend can obtain it without deserializing the SBD stream. -
X-phoss-AP-Transaction-ID(since v0.13.2): The ID of the phoss-ap transaction the document belongs to - the same value as theidfield returned by the REST API and written into the metadata sidecar of the file-based forwarders. Unlike the SBDH Instance Identifier it is unique per transaction, and it stays the same for all retries and replays of that transaction. For the copy of a self-generated outbound MLS it is the ID of the outbound transaction. -
X-Verification-Result(since v0.12.0): The verdict of the inbound verification —passed,rejectedorunverified(see Inbound Verification Result). The header is sent as soon as a verdict exists; it is absent whenverification.inbound.enabled=falseor while the verification is still deferred. A value ofrejectedmeans the document was forwarded despite its rejection, i.e.verification.inbound.rejection-forwardingis notnone. -
forwarding.http.verification-details(since v0.12.0): Iftrue, the verification findings are additionally sent as the base64 encoded JSON array of theverification_detailscolumn in the headerX-Verification-Details. Default isfalse.- The header value is limited to 4096 characters. If the issue list does not fit, the trailing issues are dropped until it does — what remains is still a valid JSON array — and
X-Verification-Details-Truncated: trueis sent alongside. The REST API stays the authoritative source for the complete list. - The header is skipped entirely if the stored details are empty or cannot be parsed as a JSON array.
- The header value is limited to 4096 characters. If the issue list does not fit, the trailing issues are dropped until it does — what remains is still a valid JSON array — and
Custom HTTP headers can be added to every forwarding request using indexed properties. This is useful for authentication (e.g., Bearer tokens, API keys) or passing custom metadata to the Receiver Backend.
-
forwarding.http.headers.{n}.name: The header name (case-insensitive).{n}is a 1-based index. -
forwarding.http.headers.{n}.value: The header value.
Headers are read sequentially starting from index 1. The sequence stops at the first missing .name entry.
Example:
forwarding.http.headers.1.name=Authorization
forwarding.http.headers.1.value=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
forwarding.http.headers.2.name=X-Tenant-ID
forwarding.http.headers.2.value=customer-42The HTTP client used for forwarding is configured via HttpClientSettingsConfig from ph-web with the prefix forwarding.. All properties below are optional and have sensible defaults.
-
forwarding.http.timeout.connect.millis(or.seconds,.minutes,.hours): Connect timeout for the HTTP POST. -
forwarding.http.timeout.response.millis(or.seconds,.minutes,.hours): Response/read timeout for the HTTP POST. -
forwarding.http.timeout.connectionrequest.millis(or.seconds,.minutes,.hours): Timeout for obtaining a connection from the pool.
-
forwarding.http.retry.count: Number of HTTP-level retries (integer, >= 0). Not set by default. -
forwarding.http.retry.interval.millis(or.seconds,.minutes,.hours): Interval between HTTP-level retries. -
forwarding.http.retry.always: Whether to retry on all failures (boolean). Default is the HttpClient default.
-
forwarding.http.proxy.enabled: Enable a separate HTTP proxy for forwarding requests. Default isfalse. -
forwarding.http.proxy.host: Proxy hostname. -
forwarding.http.proxy.port: Proxy port (integer). -
forwarding.http.proxy.username: Proxy authentication username. -
forwarding.http.proxy.password: Proxy authentication password. -
forwarding.http.proxy.nonProxyHosts: Pipe-separated list of hosts that bypass the proxy.
-
forwarding.http.tls.checks.disabled: Disable all TLS checks (hostname + certificate). Default isfalse. Security risk — use only for testing. -
forwarding.http.tls.hostname-check.disabled: Disable TLS hostname verification only. Default isfalse. -
forwarding.http.tls.certificate-check.disabled: Disable TLS certificate verification only. Default isfalse.
-
forwarding.http.useragent: Custom User-Agent header for forwarding requests. -
forwarding.http.follow-redirects: Whether to follow HTTP redirects. Default is the HttpClient default. -
forwarding.http.keep-alive: Whether to use HTTP keep-alive connections. Default is the HttpClient default. -
forwarding.http.dnsclientcache.use: Whether to use the DNS client cache. -
forwarding.http.protocol-upgrade.enabled: Enable HTTP/2 protocol upgrade. Default is the HttpClient default.
-
forwarding.s3.bucket: The S3 bucket name where received SBDs are stored. -
forwarding.s3.region: The AWS region of the S3 bucket (e.g.eu-central-1). -
forwarding.s3.access-key-id: AWS access key ID for S3 access. Optional if using IAM roles. -
forwarding.s3.secret-access-key: AWS secret access key for S3 access. Optional if using IAM roles. -
forwarding.s3.endpoint(since 0.2.2): Custom S3 endpoint URL for S3-compatible providers (e.g. MinIO, Garage). Optional — if not set, standard AWS S3 is used. -
forwarding.s3.path-style-access(since 0.2.2): Enable S3 path-style access instead of virtual-hosted style. Required by most S3-compatible providers. Default isfalse. -
forwarding.s3.key-prefix: Optional prefix (folder path) for S3 object keys. -
forwarding.s3.filename-pattern(since v0.12.0): The object key appended to the key prefix. Default is{sbdh-instance-id}, which is the layout of all previous versions. See Filename Pattern for the supported placeholders. -
forwarding.s3.write-metadata(since v0.10.2): Whether to additionally upload a JSON metadata sidecar object next to each SBD. Eithertrueorfalse(default). When enabled, the sidecar object uses the same key as the uploaded SBD but with a.jsonextension instead of.xml(e.g.<key-prefix><resolved name>.jsonalongside<key-prefix><resolved name>.xml), is stored with content typeapplication/json, and contains the same JSON metadata as the SFTP and filesystem sidecars (the full inbound transaction as returned by the inbound transaction REST API). A failure to write the sidecar is logged but does not fail the forwarding.
-
forwarding.sftp.host: The hostname or IP address of the SFTP server. -
forwarding.sftp.port: The port of the SFTP server. Default is22. -
forwarding.sftp.connectiontimeoutms: Connection timeout in milliseconds. Default is defined byISftpSettingsHost.DEFAULT_CONNECTION_TIMEOUT_MS. -
forwarding.sftp.user: The username for SFTP authentication. -
forwarding.sftp.password: The password for SFTP authentication. Either this or a key pair must be provided. -
forwarding.sftp.keypair.privatekeypath: Path to the private key file for key-based authentication. -
forwarding.sftp.keypair.publickeypath: Path to the public key file for key-based authentication. -
forwarding.sftp.keypair.passphrase: Optional passphrase for the key pair. -
forwarding.sftp.knownhostspath: Path to the SSH known_hosts file for host key verification. Optional — if not set, host key checking may be skipped. -
forwarding.sftp.maxconnections: Maximum number of parallel SFTP connections. Default is defined byISftpSettingsHost.DEFAULT_MAX_CONNECTIONS. -
forwarding.sftp.uploaddir: The remote directory on the SFTP server where SBDs are uploaded. -
forwarding.sftp.filename-pattern(since v0.12.0): The base name of the uploaded files. Default is{datetime}_{incoming-id}, which is the layout of all previous versions. See Filename Pattern for the supported placeholders. -
forwarding.sftp.write-metadata: (since v0.10.2) Whether to additionally upload a JSON metadata sidecar next to each SBD. Eithertrueorfalse(default). When enabled, the sidecar uses the same base name as the uploaded SBD but with a.jsonextension (e.g.20260702120000_abc.jsonalongside20260702120000_abc.xml) and contains the same JSON metadata as the filesystem forwarder (the full inbound transaction as returned by the inbound transaction REST API, includingid,senderID,receiverID,docTypeID,processID,as4MessageID,sbdhInstanceID,status,attemptCount,receivedDTand the remaining transaction fields). Like the SBD itself, the sidecar is written atomically (.tmpthen rename). A failure to write the sidecar is logged but does not fail the forwarding.
Writes the received SBD to a local directory. Useful for integrations where the Receiver Backend picks up files from a shared filesystem or NFS mount.
-
forwarding.filesystem.directory: (Required whenforwarding.mode=filesystem) The absolute path of the target directory. Created automatically if it does not exist. -
forwarding.filesystem.filename-pattern(since v0.12.0): The base name of the written files, resp. the name of the subdirectory in theper-transactionlayout. Default is{sbdh-instance-id}, which is the layout of all previous versions. See Filename Pattern for the supported placeholders. -
forwarding.filesystem.layout: File layout mode. Eitherflat(default) orper-transaction.-
flat— All files are written directly into the configured directory. Each transaction produces two files:{sbdhInstanceID}.xml(the SBD) and{sbdhInstanceID}.json(metadata) — with the default filename pattern. -
per-transaction— Each transaction gets its own subdirectory, named by the filename pattern and therefore by the SBDH Instance ID by default, containingsbd.xmlandmetadata.json.
-
Files are written atomically using a .tmp extension and renamed after the transfer completes, so the Receiver Backend never sees partial files. If a file with the same name already exists (e.g., due to duplicate detection in store_and_flag mode), a numeric suffix is appended to ensure uniqueness.
The metadata JSON file contains: sbdhInstanceID, senderID, receiverID, docTypeID, processID, as4MessageID, c1CountryCode, and receivedDT.
Delivery confirmation: No — MLS response code is AB (Accepted Blind), same as S3 and SFTP modes.
In addition to the primary forwarder configured under forwarding.*, up to 10 secondary forwarders can be configured to receive a copy of every successfully forwarded inbound document. Secondaries are useful for fanning out received messages to additional environments (e.g. duplicating from TEST to both a Dev and a QA backend) without affecting the primary delivery.
Semantics:
- Secondary forwarders are dispatched fire-and-forget, asynchronously, only after the primary forwarder succeeded (they never run if the primary fails).
-
No retries are performed for secondaries. If a secondary forward fails, the failure is logged at
WARN(orERRORfor exceptions) and the next secondary continues. - Secondary outcomes have no effect on the inbound transaction status, MLS response, retry scheduling, or circuit breaker.
- A failing secondary does not prevent later secondaries from running.
- No
inbound_forwarding_attemptrows are persisted for secondaries — visibility is via log messages and OpenTelemetry spans only.
Configuration:
Secondary forwarders are configured using indexed keys with the prefix forwarding.secondary.{n}., where {n} is a 1-based index in the range 1–10. Iteration starts at 1 and stops at the first index where no .mode key is set. Defining a secondary at index 11 or higher causes the AP to fail to start with an InitializationException. Each secondary uses the same key suffixes as the primary, just with the new prefix.
-
forwarding.secondary.{n}.mode: (Required to define a secondary) The forwarding mode for this secondary. Same values asforwarding.mode:http_post_sync,http_post_async,s3_link,sftp,filesystem,spi.
All other forwarder-specific keys are read with the forwarding.secondary.{n}. prefix in place of forwarding.. For example:
- HTTP:
forwarding.secondary.{n}.http.endpoint,forwarding.secondary.{n}.http.headers.1.name,forwarding.secondary.{n}.http.timeout.connect.millis, etc. - S3:
forwarding.secondary.{n}.s3.bucket,forwarding.secondary.{n}.s3.region, etc. - SFTP:
forwarding.secondary.{n}.sftp.host,forwarding.secondary.{n}.sftp.user, etc. - Filesystem:
forwarding.secondary.{n}.filesystem.directory,forwarding.secondary.{n}.filesystem.layout. - SPI (since v0.10.0):
forwarding.secondary.{n}.spi.id, plus any keys read by the SPI-provided forwarder using the sameforwarding.secondary.{n}.prefix.
Secondaries may use a different mode from the primary and from each other.
Example — primary HTTP plus two HTTP secondaries (Dev + QA):
# Primary - mandatory, drives SLA and retries
forwarding.mode=http_post_sync
forwarding.http.endpoint=https://prod-backend.example/inbound
# Secondary #1 - Dev environment
forwarding.secondary.1.mode=http_post_async
forwarding.secondary.1.http.endpoint=https://dev-backend.example/inbound
forwarding.secondary.1.http.headers.1.name=Authorization
forwarding.secondary.1.http.headers.1.value=Bearer dev-token
# Secondary #2 - QA environment
forwarding.secondary.2.mode=http_post_async
forwarding.secondary.2.http.endpoint=https://qa-backend.example/inboundExample — primary HTTP plus a filesystem secondary for archival:
forwarding.mode=http_post_sync
forwarding.http.endpoint=https://prod-backend.example/inbound
forwarding.secondary.1.mode=filesystem
forwarding.secondary.1.filesystem.directory=/var/lib/phoss-ap/archive
forwarding.secondary.1.filesystem.layout=per-transactionObservability: Each secondary dispatch is wrapped in a phoss.ap.inbound.forward.secondary OpenTelemetry span carrying the phoss.ap.transaction.id, phoss.ap.sbdh.instance_id, and phoss.ap.forwarder.index attributes; the inner forwarder-specific span (phoss.ap.forwarder.dispatch) is set as today.
MLS documents received from other APs are forwarded to C4 like any other inbound document. The MLS that this AP generates itself — the AP, AB or RE it sends back to C2 — never left the AP before v0.12.0. It can now be copied to a separate sink, e.g. a reporting integration that builds a Tax Data Summary from the validation detail it carries.
Semantics:
-
Disabled by default. Without
forwarding.mls-copy.enabled=truenothing is dispatched and nothing is logged. - The copy is dispatched fire-and-forget once the outbound MLS transaction has been created — for
AP,ABandREalike. Note that this is the MLS the AP generated, not proof that C2 received it: the copy goes out even if the subsequent AS4 transmission to C2 fails. - A failure is logged at
WARN(orERRORfor exceptions) and affects neither the MLS sending to C2 nor the inbound transaction status. Noinbound_forwarding_attemptrows are written and there are no retries. - The payload is the bare
ApplicationResponseXML as it is stored. It is deliberately not wrapped in an SBDH: the AP never materializes an SBD itself — phase4 adds the envelope during the AS4 transmission — so wrapping would mean generating and storing a second document per MLS. A consumer distinguishes the shapes via thekindof the forwardable document, which isoutbound-mls-copyhere; see Forwardable Kind. - No metadata JSON sidecar is written for an MLS copy, so the
s3,sftpandfilesystemsinks store only the document. TheApplicationResponsealready references the document it responds to.
Configuration:
-
forwarding.mls-copy.enabled:trueto enable the dispatch. Default isfalse. -
forwarding.mls-copy.mode: The forwarding mode of the sink. Same values asforwarding.mode:http_post_sync,http_post_async,s3_link,sftp,filesystem,spi. If this key is absent, the complete primary forwarder configuration is reused — mode and all forwarder-specific keys underforwarding.— soforwarding.mls-copy.enabled=truealone sends the copies to the primary sink.
All forwarder-specific keys are read with the forwarding.mls-copy. prefix in place of forwarding., exactly like for the secondary forwarders, e.g. forwarding.mls-copy.http.endpoint or forwarding.mls-copy.spi.id.
Example — MLS copies to a dedicated reporting endpoint:
forwarding.mode=http_post_sync
forwarding.http.endpoint=https://prod-backend.example/inbound
forwarding.mls-copy.enabled=true
forwarding.mls-copy.mode=http_post_async
forwarding.mls-copy.http.endpoint=https://reporting.example/mlsExample — MLS copies to the primary sink:
forwarding.mode=http_post_async
forwarding.http.endpoint=https://prod-backend.example/inbound
# No .mode key - the primary configuration above is reused
forwarding.mls-copy.enabled=trueObservability: Each dispatch is wrapped in a phoss.ap.mls.forward.copy OpenTelemetry span carrying the phoss.ap.transaction.id, phoss.ap.sbdh.instance_id and phoss.ap.mls.response_code attributes.
Retry behavior is defined via customizable Java interfaces. The following properties configure the default retry strategy.
-
retry.sending.max-attempts: Maximum number of AS4 sending attempts for outbound transactions before marking as permanently failed. Default is3. -
retry.sending.initial-backoff(since 0.9.0): Initial backoff interval between sending retries. Default is3m. -
retry.sending.initial-backoff.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is180000. -
retry.sending.backoff-multiplier: Multiplier applied to the backoff interval after each failed attempt (exponential backoff). Default is2.0. -
retry.sending.max-backoff(since 0.9.0): Maximum backoff interval. Default is1h. -
retry.sending.max-backoff.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is3600000. -
retry.forwarding.max-attempts: Maximum number of forwarding attempts for inbound transactions before marking as permanently failed. Default is3. -
retry.forwarding.initial-backoff(since 0.9.0): Initial backoff interval between forwarding retries. Default is1m. -
retry.forwarding.initial-backoff.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is60000. -
retry.forwarding.backoff-multiplier: Multiplier applied to the backoff interval after each failed forwarding attempt. Default is2.0. -
retry.forwarding.max-backoff(since 0.9.0): Maximum backoff interval for forwarding retries. Default is1h. -
retry.forwarding.max-backoff.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is3600000. -
retry.scheduler.interval(since 0.9.0): Interval at which the retry scheduler checks for transactions eligible for retry. Default is1m. Safe to enable on all instances — usesSKIP LOCKEDfor work distribution. -
retry.scheduler.interval.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is60000. -
retry.scheduler.batch-size(since 0.1.2): Maximum number of transactions to retry per scheduler cycle (inbound and outbound each). Must be >= 1. Default is50.
Circuit breakers for the SMP lookup (smp$<host>), the outbound AS4 sending (ap$<endpoint URL>) and the inbound forwarding (phoss-ap-forwarder). Implemented in-memory using Failsafe. See Retry and Resilience Patterns for details.
The settings apply to every circuit breaker; they are read when a circuit breaker is created, so a change only takes effect for keys that are used for the first time afterwards, or after a restart, or after a reset via POST /api/ops/circuit-breakers/{circuitKey}/reset.
-
circuit-breaker.failure-threshold: Number of consecutive failures before the circuit breaker opens. Default is5. -
circuit-breaker.open-duration(since 0.9.0): Duration the circuit breaker stays open before transitioning to half-open. Default is1m. -
circuit-breaker.open-duration.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is60000. -
circuit-breaker.half-open-max-attempts: Number of probe attempts allowed through in half-open state to test if the remote system has recovered. Default is1. -
circuit-breaker.defer-max-duration(since 0.13.0): Maximum age of a transaction for which a rejection by a circuit breaker is deferred without consuming a retry attempt. Older transactions fall back to the regular attempt counting, so that a permanently unreachable SMP or AP cannot defer a transaction forever. Default is12h.
By default the circuit breaker opens after circuit-breaker.failure-threshold consecutive failures, which can already be reached during a short load peak on an otherwise healthy SMP or AP. Two optional alternatives are available; both are unset by default, so the behaviour is unchanged unless they are configured.
-
circuit-breaker.failure-executions: Number of executions the failure threshold is measured over, so that the failures no longer have to be consecutive. Must be greater than or equal tocircuit-breaker.failure-threshold; an invalid value is logged as an error and ignored. Unset by default. -
circuit-breaker.failure-period: Rolling time window the failure threshold is measured over. Must be at least10ms. Unset by default. -
circuit-breaker.failure-rate: Failure rate in percent (1-100) at which the circuit breaker opens. Only used together withcircuit-breaker.failure-period; it replaces the absolute failure threshold. Unset by default.
Note:
circuit-breaker.failure-executionsandcircuit-breaker.failure-periodalone make the circuit breaker more sensitive, not less, because the failures inside the window no longer have to be consecutive. Onlycircuit-breaker.failure-ratetolerates isolated failures. A typical tolerant configuration iscircuit-breaker.failure-rate=50,circuit-breaker.failure-executions=20andcircuit-breaker.failure-period=5m: the circuit breaker opens if at least half of the last 20 or more executions within 5 minutes failed.
-
verification.outbound.enabled: Enable or disable optional verification of outbound documents before AS4 sending. Default isfalse. -
verification.inbound.enabled: Enable or disable optional verification of inbound documents before forwarding. Default isfalse. -
verification.verifier-fail-mode(since v0.12.0): What to do when an inbound document verifier cannot make a verdict, because its backend service is unavailable (connection refused, HTTP 5xx, timeout). Possible values:-
closed(default) — the document is rejected and a negative MLS (RE) is sent to C2. This is the behaviour of all versions before v0.12.0. -
open— the unverified document is forwarded to C4 and a warning is logged for every such document. TheonInboundVerificationAcceptedlifecycle callback is deliberately not fired, because nothing was verified. -
deferred— the transaction is set to statusverification_deferredand re-verified later; no MLS is sent while the verification is deferred. The mode is only applied to unavailable verifier services. A verifier that inspected the document and rejected it always leads to a rejection.
-
-
verification.inbound.rejection-forwarding(since v0.12.0): What to do with an inbound document that did not pass the verification. The rejection itself is unaffected: the verdictrejectedis always recorded inverification_resultand the negative MLS (RE) is always sent to C2 — this only decides if the rejected document nevertheless reaches C4. Possible values:-
none(default) — the document is never forwarded and the transaction ends in statusrejected. This is the behaviour of all versions before v0.12.0. -
best-effort— a fire-and-forget copy is dispatched to the primary and to all secondary forwarders. The transaction still ends in statusrejected, noinbound_forwarding_attemptrow is written and a failing forwarder is only logged — there is no retry. -
retry— the document runs through the regular forwarding state machine including the retries, so the transaction ends in statusforwardedorpermanently_failedwhileverification_resultstaysrejected. This exists for deployments in which C4 must be able to act on a document that failed the content validation — e.g. the UAE Peppol DCTCE model, where C3 has to produce a Tax Data Summary instead of a Tax Data Document for such an invoice. The mode applies to every rejection verdict, so also to a document rejected because a verifier was unavailable andverification.verifier-fail-mode=closedwas configured, and to one whoseverification.deferred.max-durationwas exceeded. In the modesbest-effortandretryC2 receives exactly one MLS, namely theREof the rejection; the later positive MLS and the "forwarding failed" MLS are suppressed. In moderetryan inbound Peppol Reporting item is created on successful forwarding, exactly as for any other forwarded document. See Verification Rejection Forwarding and Receiving Process.
-
-
verification.deferred.retry-interval(since v0.12.0): Only used withverification.verifier-fail-mode=deferred. The interval between two re-verification attempts. Default is5m. -
verification.deferred.max-duration(since v0.12.0): Only used withverification.verifier-fail-mode=deferred. The maximum duration — relative to the reception of the document — for which the verification may be deferred. Afterwards the document is rejected and a negative MLS is sent to C2, so that C2 finally gets an answer. Default is12h. -
verification.phorm.url: Base URL of the phorm Validation Service used for document verification (e.g.http://localhost:8080). Default ishttp://localhost:8080. -
verification.phorm.token: TheX-Tokenheader value required by the phorm Validation Service for authentication. If empty, no token is sent.
The built-in verification implementation (phoss-ap-validation module) calls the phorm Validation Service's /api/dd_and_validate/ endpoint. The service auto-detects the document type and validates it against Peppol BIS rules. Since v0.12.0 a document whose document type ID uses a non-XML syntax specific ID and whose payload is a PDF — a French Factur-X invoice — is sent to the /api/hybrid_validate endpoint instead, together with the SBDH C1 country code as the country query parameter; see Architecture Overview. If the overall validation conformance is INVALID, the document is rejected. If the phorm service cannot be called at all (HTTP error, timeout, connection refused), the document is not rejected since v0.12.0 — the service is reported as unavailable and verification.verifier-fail-mode decides what happens. Custom verification logic can also be provided via the pluggable IInboundDocumentVerifierSPI / IOutboundDocumentVerifierSPI (both extending IDocumentVerifier).
-
mls.sending.enabled(since 0.1.2): Global switch to enable or disable all outbound MLS sending. When set tofalse, the AP will not send any MLS responses (neither success nor failure) for inbound transactions. Default istrue. -
mls.type: The MLS type this AP requests for its outbound documents (as C2) - it is placed in the SBDH. EitherFAILURE_ONLYorALWAYS_SEND. Default isALWAYS_SEND. It has no effect on inbound documents (as C3): there theMLS_TYPEof the received SBDH applies, orFAILURE_ONLYif it is missing or invalid. -
mls.sending.trigger(since 0.13.0): What triggers the positive MLS of a successfully forwarded document. Eitherautoorapi. Default isauto.-
auto— the MLS is sent immediately after the forwarding to C4 succeeded. This is the behaviour of all versions before 0.13.0. -
api— the MLS is only sent when the Receiver Backend reports the outcome viaPOST /api/mls/send. If it stays silent, the watchdog sends the fallback MLS aftermls.sending.api.timeout.
Only the success path is deferred. The negative MLS (
RE) of a failed inbound verification and the acknowledging MLS (AB) of exhausted forwarding retries are always sent automatically, because in both cases the document never reached the backend and it can therefore not report anything.This is meant for a C3 that forwards to a middleware in front of C4: a
2xxfrom the middleware is not proof that the document reached C4, so theAPsent at that moment states a fact that has not happened yet. See Message Level Status for the full picture. -
-
mls.sending.api.timeout(since 0.13.0): Only used withmls.sending.trigger=api. If the Receiver Backend has not reported a status within this duration after the reception of the document, the AP sends the fallback MLS on its own. Duration grammar (ns,us,ms,s,m,h,d). Default is5m. A non-positive value is rejected with a warning and replaced by the default. The check runs as part of the retry scheduler cycle, so the effective delay is up to oneretry.scheduler.intervallonger.The window is measured from the reception and not from the forwarding, because that is where the MLS-1 clock starts. A forwarding that itself took long therefore shortens the window of the backend instead of extending the SLA budget — in the extreme case the fallback goes out with the first cycle after the forwarding.
Do not raise this carelessly. MLS-1 of the Peppol Network Policy requires 99.5 % of all MLS responses within 20 minutes, measured from the reception of the business document — and the fallback MLS still has to be built, sent via AS4 and possibly retried within what is left. The default of 5 minutes keeps that margin; whoever widens it takes on the SLA risk.
-
mls.sending.api.timeout.code(since 0.13.0): The MLS response code used for the fallback. EitherAB(acknowledging) orAP(acceptance). Default isAB, which is the semantically correct one: the document was accepted, but the delivery to C4 was never confirmed.REis deliberately not allowed — a rejection is a statement only the Receiver Backend can make — and is rejected with a warning.
-
peppol.reporting.schedule.enabled: Enable or disable the reporting schedule. Default istrue. -
peppol.reporting.schedule.day-of-month: Day of the month on which reporting is submitted (1-31). Default is2. -
peppol.reporting.schedule.hour: Hour of the day at which reporting is submitted (0-23). Default is6. -
peppol.reporting.schedule.minute: Minute of the hour at which reporting is submitted (0-59). Default is7. -
peppol.reporting.exclude.participant-ids(since 0.13.0): Comma separated list of participant identifiers that are excluded from Peppol Reporting - e.g. the participants used for synthetic monitoring transactions. Each entry is either URI encoded (iso6523-actorid-upis::9915:test) or uses the default participant identifier scheme only (9915:test). A transaction is excluded if the configured identifier matches the sender or the receiver, in the inbound as well as in the outbound direction. Matching transactions are not stored in the Peppol Reporting backend and get the reporting statusexcludedinstead ofreported; they are archived like any other completed transaction. Unparsable values are logged as an error once and ignored. Default is empty, so nothing is excluded. Note: Peppol Network Reporting is meant to cover every transaction exchanged over the Peppol network, so excluding real network traffic is a deliberate deviation that the operator of the AP owns.
-
duplicate.detection.as4.mode: Behavior when an AS4 Message ID duplicate is detected. Eitherrejectorstore_and_flag. Default isreject. -
duplicate.detection.sbdh.mode: Behavior when an SBDH Instance Identifier duplicate is detected. Eitherrejectorstore_and_flag. Default isreject. -
phase4.incoming.duplicatedisposal: Duration that AS4 Message IDs of received messages are kept in phase4's in-memory duplicate cache. Within this window an incoming message with a previously seen AS4 Message ID is detected as a duplicate (and then handled according toduplicate.detection.as4.mode). Accepts the duration grammar (e.g.10m,1h 30m) — see the Duration values section at the top of this page. Default is10m. Defined by phase4 (AS4Configuration). -
phase4.incoming.duplicatedisposal.minutes(deprecated; scheduled for removal in a future phase4 major version): Legacy minute-typed key for the same setting. A plain integer (minutes). Default is10. When set, a deprecation warning is logged at startup; prefer the unit-lessphase4.incoming.duplicatedisposalform above.
-
archival.scheduler.enabled: Enable or disable the periodic archival scheduler. Default istrue. Safe to enable on all instances — usesSKIP LOCKEDfor work distribution. -
archival.scheduler.interval(since 0.9.0): Interval at which the archival scheduler runs. Default is1h. -
archival.scheduler.interval.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is3600000. -
archival.scheduler.batch-size(since 0.1.2): Maximum number of transactions to archive per scheduler cycle (inbound and outbound each). Must be >= 1. Default is100.
Periodically deletes archived inbound and outbound transactions whose completed_dt is older than the configured retention. Both the database row (and its archived attempt rows) and the associated document file are removed. Cleanup operates only on archive tables — primary tables are never touched.
Preconditions (validated at startup, otherwise InitializationException):
-
archival.scheduler.enabledmust betrue. Cleanup only operates on already-archived rows; it cannot run without the archiver feeding it. -
cleanup.scheduler.retentionmust be at least 2 days. -
cleanup.scheduler.retentionmust be greater than2 × archival.scheduler.interval, so the archiver has time to land completed transactions before cleanup considers them.
Document file deletion is attempted before the row is deleted: a transient storage error leaves the row for the next cycle so that orphan files cannot accumulate.
Duration values use suffixes: ns, us, ms, s, m, h, d. Examples: 90d, 2d 12h, 36h. See the ph-config Duration values format.
-
cleanup.scheduler.enabled: Enable or disable the cleanup scheduler. Default isfalse. -
cleanup.scheduler.interval: Interval at which the cleanup scheduler runs. Default is24h. -
cleanup.scheduler.retention: Retention duration. Archived transactions whosecompleted_dtis older than this are eligible for cleanup. Default is90d. Minimum is2d. -
cleanup.scheduler.batch-size: Maximum number of archived transactions to clean up per scheduler cycle (inbound and outbound each). Must be >= 1. Default is100.
Optional directory-based batch sender that watches a directory for pre-built SBD (Standard Business Document) XML files and submits them via the normal outbound pipeline. Files are moved to success/ or error/ subdirectories after processing, with a JSON result file written alongside each. A pending/ subdirectory is used during processing to prevent duplicate pickup.
-
dirsender.enabled: Enable or disable the directory sender. Default isfalse. -
dirsender.directory: (Required when enabled) Absolute path to the watch directory. The directory must exist. Subdirectoriespending/,success/, anderror/are created automatically. -
dirsender.scan-interval(since 0.9.0): Interval between directory scans. Default is30s. -
dirsender.scan-interval.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is30000. -
dirsender.initial-delay(since 0.9.0): Delay before the first scan after startup. Default is30s. -
dirsender.initial-delay.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is30000.
Only files with the .xml extension in the watch directory root are picked up. Files must be complete, valid Peppol SBD documents (SBDH envelope + business document). Raw business documents without SBDH are not supported — use the REST API (POST /api/outbound/submit/...) for those.
On startup, files found in the pending/ directory from a previous run are recovered: the database is checked to determine whether each file was already submitted, and files are moved to the appropriate target directory. Files with no database entry are moved back to the watch directory for reprocessing.
These settings are needed when the AP runs behind an outbound proxy server. They affect AS4 sending, SMP lookups, CRL downloads, and reporting submission.
-
http.proxy.enabled: Kill switch for the outbound HTTP proxy. The proxy is used as soon as host and port are set; only an explicitfalsedisables it again. -
http.proxy.host: Proxy host name or IP address. -
http.proxy.port: Proxy port number. -
http.proxy.username: Optional username for proxy authentication. -
http.proxy.password: Optional password for proxy authentication. -
http.proxy.nonProxyHosts: Pipe-separated list of hosts that should bypass the proxy (e.g.localhost|127.0.0.1). The host names are matched exactly — wildcards like*.example.comare not supported.
Since v0.12.0 a configured proxy is additionally installed as the JVM wide default ProxySelector at startup. This is required because not every outbound connection goes through Apache HttpClient: the certificate revocation check that the JSSE PKIX trust manager performs during a TLS handshake downloads the CRL and contacts the OCSP responder through the JDK internal URLConnection, which only honours the default ProxySelector. Without it, TLS handshakes towards SMPs and APs hang on the CRL fetch and then fail with a CertPathValidatorException in a proxy-only network.
If http.proxy.username is set, the credentials are also registered as the JVM default Authenticator, restricted to proxy authentication requests. Note that the JDK refuses Basic proxy authentication for HTTPS tunnels by default; since CRL distribution points and OCSP responders are addressed via http://, this does not apply to the revocation checks.
-
shutdown.timeout(since 0.9.0): Maximum time to wait for in-flight sending and forwarding operations to complete during graceful shutdown. After this timeout, remaining tasks are interrupted. Default is30s. -
shutdown.timeout.ms(deprecated since 0.9.0; removal scheduled for v1.0.0): Legacy millisecond-typed key. Default is30000. -
startup.recovery.enabled: Enable or disable stale transaction recovery at startup. Whentrue, transactions left in transient states (sending,forwarding) from a previous unclean shutdown are reset to their retry-eligible states (failed,forward_failed) withnext_retry_dt = NOW(). Default istrue.
See also the Spring Boot properties server.shutdown and spring.lifecycle.timeout-per-shutdown-phase in the Spring Boot / Server Configuration section.
The AP exposes a JSON status endpoint at GET /management/status that returns non-sensitive configuration values, version information, and runtime metadata. It is intended for health probes, dashboards, and operational tooling.
-
management.status.enabled(since v0.1.3): Enable or disable the status endpoint. Whenfalse, the endpoint returns{"status.enabled": false}instead of the full status data. Default istrue.
See API Specification for the full response schema.
-
phase4.dump.path: Directory path where AS4 message dumps (request/response) are stored for debugging purposes. If not set, no dumps are created. Example:/var/phoss-ap/data/phase4-dumps/. -
phase4.dump.mode: Controls the file layout of AS4 message dumps. Eitherdirectionorgrouped. Default isdirection.-
direction— the existing behavior: dumps are placed into separateincoming/andoutgoing/subdirectories. -
grouped— correlated messages of a single AS4 exchange (UserMessage + SignalMessage) are placed together in a shared directory undergrouped/<date>/<correlation-id>/. For inbound exchanges the correlation ID is the internal unique request ID; for outbound exchanges it is the sanitized AS4 Message ID.
-
Optional integration with Sentry for real-time error tracking and alerting. Sentry is fully disabled by default and activates only when the sentry.dsn property is set and the sentry-logback dependency is on the classpath.
When active, a Logback SentryAppender is registered automatically. Additionally, a SentryNotificationHandler is registered that reports business-level failure events (e.g. sending failures, forwarding failures, verification rejections, reporting failures) to Sentry with structured attributes.
-
sentry.dsn: The Sentry Data Source Name (DSN) for your project (e.g.https://<key>@sentry.io/<project>). If not set, Sentry is fully disabled. Treat as a secret — place it inapplication-private.propertiesor inject via environment variable. -
sentry.send-default-pii: Enable sending of Personally Identifiable Information such as request headers and IP addresses. See the Sentry PII documentation for details. Default istrue. -
sentry.logging.minimum-event-level: The minimum Logback level at which log events are sent to Sentry as events. Default isERROR. -
sentry.logging.minimum-breadcrumb-level: The minimum Logback level at which log events are captured as Sentry breadcrumbs. Default isINFO.
Optional integration with OpenTelemetry for metrics, traces and logs. See OpenTelemetry Integration for the topology, emitted metrics, span names, attribute keys, and configuration via standard OTel environment variables.
-
otel.enabled(since 0.9.0): Set totrueto bootstrap the OpenTelemetry SDK at application startup via the autoconfigure module. Default isfalse. All other behavior (OTLP endpoint, protocol, resource attributes, sampler, exporters) is configured via the standard OpenTelemetry environment variables / system properties — for exampleOTEL_SERVICE_NAME,OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_EXPORTER_OTLP_PROTOCOL,OTEL_RESOURCE_ATTRIBUTES. See the OpenTelemetry Java SDK configuration reference for the full list.
Standard Spring Boot properties for configuring the embedded server.
-
server.port: The HTTP port on which the AP listens. Default is8080. -
server.shutdown: Shutdown mode. Set togracefulto enable graceful shutdown (stop accepting new requests, drain in-flight requests). Default isimmediate. -
spring.lifecycle.timeout-per-shutdown-phase: Maximum time Spring Boot waits for the servlet container to drain in-flight HTTP requests during graceful shutdown. Example:30s. Default is30s. -
server.forward-headers-strategy: Strategy for handling forwarded headers when running behind a reverse proxy. Set tonativewhen behind a proxy. -
spring.servlet.multipart.max-file-size: Maximum file size for multipart uploads (document submission). Example:100MB. -
spring.servlet.multipart.max-request-size: Maximum request size for multipart uploads. Example:100MB.