-
Notifications
You must be signed in to change notification settings - Fork 18
Forwarding Process
After an inbound document is received and stored, the AP forwards it to the Receiver Backend (C4). The forwarding mode is selected once per AP instance via the forwarding.mode configuration property. Four modes are available, each with different trade-offs regarding delivery confirmation, infrastructure requirements, and reporting flow.
| Mode | Config value | Transport | Delivery confirmation | Reporting trigger |
|---|---|---|---|---|
| HTTP POST Async | http_post_async |
HTTP POST | Yes (HTTP 200) | Asynchronous callback |
| HTTP POST Sync | http_post_sync |
HTTP POST | Yes (HTTP 200 + JSON) | Synchronous (C4 country code in response) |
| S3 | s3_link |
Amazon S3 PutObject | No | Asynchronous callback |
| SFTP | sftp |
SFTP file upload | No | Asynchronous callback |
The delivery confirmation flag determines the MLS response code sent back to C2 on successful forwarding:
- With delivery confirmation (HTTP modes): MLS response code AP (Approved)
- Without delivery confirmation (S3, SFTP): MLS response code AB (Accepted Blind)
The SBD is POSTed to the Receiver Backend. The AP only checks for a successful HTTP status code (2xx). The response body is ignored.
-
Method:
POST -
URL: Configured via
forwarding.http.endpoint -
Content-Type:
application/xml - Body: The raw SBD XML (SBDH envelope + business document)
-
HTTP Status:
2xx(any successful status) - Body: Ignored
The Receiver Backend must call back to the AP's inbound reporting API to provide the C4 country code:
POST /api/inbound/report?sbdhInstanceID={id}&c4CountryCode={code}
with the X-Token header set to the configured API token. This triggers the Peppol Reporting record creation.
| Property | Required | Description |
|---|---|---|
forwarding.mode |
Yes | http_post_async |
forwarding.http.endpoint |
Yes | Target URL of the Receiver Backend |
The SBD is POSTed to the Receiver Backend. The response must contain a JSON object with the C4 country code, enabling immediate Peppol Reporting without a separate callback.
-
Method:
POST -
URL: Configured via
forwarding.http.endpoint -
Content-Type:
application/xml - Body: The raw SBD XML (SBDH envelope + business document)
-
HTTP Status:
2xx -
Content-Type:
application/json -
Body: A JSON object containing at minimum the
countryCodeC4field
{
"countryCodeC4": "DE"
}{
"countryCodeC4": "AT",
"documentId": "a4f7e2b1-8c3d-4e5f-9a6b-1c2d3e4f5a6b",
"status": "received"
}Only the countryCodeC4 field is read by the AP. Any additional fields are ignored.
If the Receiver Backend cannot process the document, it should return an HTTP error status (4xx or 5xx). Optionally, a JSON body can be included:
{
"error-source": "Sbdh parsing",
"error-message": "Invalid SBDH envelope",
"status": "rejected"
}The AP treats any non-2xx status as a forwarding failure and schedules a retry.
Triggered immediately and automatically by the AP when the forwarding response contains a countryCodeC4 value. No separate callback is needed.
| Property | Required | Description |
|---|---|---|
forwarding.mode |
Yes | http_post_sync |
forwarding.http.endpoint |
Yes | Target URL of the Receiver Backend |
The SBD is uploaded to an Amazon S3 bucket. The Receiver Backend is expected to pick up documents from S3 independently (e.g., via S3 event notifications or polling).
-
Bucket: Configured via
forwarding.s3.bucket -
Key:
{key-prefix}{sbdhInstanceID}.xml(e.g.,inbound/550e8400-e29b-41d4-a716-446655440000.xml) -
Content-Type:
application/xml - Authentication: Either explicit access key + secret, or IAM role-based (if credentials are omitted)
The Receiver Backend must call back to the AP's inbound reporting API after processing:
POST /api/inbound/report?sbdhInstanceID={id}&c4CountryCode={code}
| Property | Required | Description |
|---|---|---|
forwarding.mode |
Yes | s3_link |
forwarding.s3.bucket |
Yes | S3 bucket name |
forwarding.s3.region |
Yes | AWS region (e.g., eu-central-1) |
forwarding.s3.access-key-id |
No | AWS access key. If empty, IAM role-based auth is used. |
forwarding.s3.secret-access-key |
No | AWS secret key. Paired with access-key-id. |
forwarding.s3.key-prefix |
No | Optional prefix for S3 keys (e.g., inbound/). A trailing / is auto-appended if missing. |
The SBD is uploaded to the Receiver Backend via SFTP. An atomic write pattern is used: the file is first written with a .tmp extension and renamed after the transfer completes, so the Receiver Backend never sees partial files.
-
Target directory: Configured via
forwarding.sftp.server-directory-upload -
Filename:
{yyyyMMddHHmmss}_{incomingID}.xml(e.g.,20260327143052_abc123.xml) -
Transfer pattern: Write to
filename.xml.tmp, then rename tofilename.xml
The Receiver Backend must call back to the AP's inbound reporting API after processing:
POST /api/inbound/report?sbdhInstanceID={id}&c4CountryCode={code}
| Property | Required | Description |
|---|---|---|
forwarding.mode |
Yes | sftp |
forwarding.sftp.host |
Yes | SFTP server hostname |
forwarding.sftp.port |
Yes | SFTP server port (typically 22) |
forwarding.sftp.username |
Yes | SFTP username |
forwarding.sftp.password |
Yes | SFTP password |
forwarding.sftp.server-directory-upload |
Yes | Remote directory for uploads |
Every forwarding attempt follows the same lifecycle regardless of mode:
RECEIVED ──> FORWARDING ──> FORWARDED (success)
│
└──> FORWARD_FAILED (retry scheduled)
│
└──> PERMANENTLY_FAILED (max retries exhausted)
- RECEIVED — Document stored, awaiting forwarding.
- FORWARDING — Forwarding attempt in progress.
- FORWARDED — Successfully delivered to Receiver Backend.
- FORWARD_FAILED — Attempt failed, retry scheduled with exponential backoff.
- PERMANENTLY_FAILED — Maximum retries exhausted. An MLS rejection (RE) with status reason FD (Failure of Delivery) is sent back to C2.
Each attempt (success or failure) is recorded in the inbound_forwarding_attempt table with:
-
attempt_dt— When the attempt occurred -
attempt_status—successorfailed -
error_code— Machine-readable error classification (null on success) -
error_details— Human-readable error description (null on success)
Each forwarding mode produces specific error codes on failure:
| Mode | Error code | Meaning |
|---|---|---|
| HTTP | http_status |
Non-2xx HTTP status code |
| HTTP | http_response_error |
Response body could not be parsed as JSON (sync mode) |
| HTTP | http_io_error |
Network or I/O error during HTTP communication |
| HTTP | http_error |
Unexpected exception during HTTP forwarding |
| HTTP | http_configuration_error |
Invalid forwarding mode configuration |
| S3 | s3-error |
S3 SDK HTTP response indicates failure |
| S3 | s3_error |
Exception during S3 upload |
| SFTP | sftp_execution |
SFTP upload command failed |
| SFTP | sftp_error |
JSch connection or transmission error |
| SFTP | sftp_exception |
Unexpected exception during SFTP upload |
| General | forward_exception |
Internal exception caught by the orchestrator |
Failed forwarding attempts are retried with exponential backoff. A periodic scheduler checks for retry-eligible transactions.
| Property | Default | Description |
|---|---|---|
retry.forwarding.max-attempts |
3 |
Maximum number of forwarding attempts before permanent failure |
retry.forwarding.initial-backoff.ms |
60000 (1 min) |
Delay before the first retry |
retry.forwarding.backoff-multiplier |
2.0 |
Multiplier applied to the backoff for each subsequent retry |
retry.forwarding.max-backoff.ms |
3600000 (1 hour) |
Upper bound on retry delay |
retry.scheduler.interval.ms |
60000 (1 min) |
How often the scheduler checks for retry-eligible transactions |
The retry scheduler uses SELECT ... FOR UPDATE SKIP LOCKED to safely support multiple AP instances processing retries concurrently.
Forwarding is protected by a circuit breaker (phoss-ap-forwarder) that temporarily halts forwarding attempts when repeated failures are detected, preventing cascading failures.
| Property | Default | Description |
|---|---|---|
circuit-breaker.failure-threshold |
5 |
Consecutive failures before the circuit opens |
circuit-breaker.open-duration.ms |
60000 (1 min) |
How long the circuit stays open before allowing a test request |
circuit-breaker.half-open-max-attempts |
1 |
Number of test requests allowed in half-open state |
It is appreciated if you star the GitHub project if you like it.
Donation link: https://paypal.me/PhilipHelger
- Home
- News and noteworthy
- Running phoss AP
- Architecture Overview
- API Specification
- Configuration Properties
- Code Lists
- Database Design Notes
- Maven Module Structure
- Runtime Extensions
- OpenTelemetry Integration
- Security Considerations
- Peppol Specifics
- Testing Without Peppol Network
- Known Users
- Migrating from phase4-peppol-standalone
- Contributing