Business Gateway
For platforms

Receiving

Take delivery of documents addressed to the companies under your account.

Inbound documents arrive from the network, are validated, and are then held for you. Nothing expires because you were slow to collect it, and nothing is handed over twice unless you ask for it again.

You find out that something arrived in one of three ways: a webhook, the event stream, or polling. Webhooks are what most integrations use.

Webhooks

Create one endpoint for the whole account, or one per company. An endpoint without companyId receives events for every company under your account; with it, only that company's.

curl -sS -X POST https://api.businessgateway.se/v1/webhooks \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{
    "endpoint": "https://hooks.example.com/peppol",
    "secret": "…",
    "timeoutMs": 10000
  }'

The endpoint must be https:// and publicly resolvable. Timeout is between 1 and 30 seconds. secret is optional — at least 16 characters if you give one; leave it out and we generate it. The create response is the only place it is shown.

From a deployment script, PUT /v1/webhooks with the same body is the idempotent version: the endpoint with that URL and companyId is updated in place (200, created: false, the secret only changing if you send one), or created if there is none (201, created: true, with the secret). Run it on every deploy. A URL that changes is a new endpoint, so delete the old one.

A payload looks like this, and always names the account and company it belongs to:

{
  "id": "…",
  "type": "message.inbound.received",
  "accountId": "…",
  "companyId": "…",
  "documentId": "…",
  "status": "VALID"
}
typeWhen
message.inbound.receivedA document is ready for you
message.inbound.validation_failedAn inbound document failed validation
message.outbound.as4_acceptedThe recipient's access point took your document
message.outbound.failedTerminal outbound failure, validation included
message.mlsA Message Level Status came back from the recipient's provider

status is the document's status when the event was raised. A document of a type with no rules to check has UNVALIDATED there and "unvalidated": true alongside.

message.mls also carries the status itself — mlsEventId, responseCode (AP, AB or RE), reasons (the distinct SV/BV/BW/FD codes) and issues ([{location, reasonCode, description}]). What those mean is in Recipient status.

Verifying the signature

Every request carries X-Peppol-Signature: sha256= followed by the hex HMAC-SHA256 of timestamp + "." + body, keyed with your secret. The timestamp is in X-Peppol-Timestamp.

Compute it over the raw body as received, not over JSON you parsed and re-serialised — re-serialising changes bytes and the signature will not match. Reject timestamps far from now, so an old request cannot be replayed at you.

Also present: X-Peppol-Event-ID and X-Peppol-Event-Type.

Delivery and failure

Deliveries go PENDING → RETRY → DELIVERED, DEAD_LETTER or CANCELLED. Your endpoint returning a non-2xx, or timing out, causes a retry — immediately, then with growing gaps up to a day apart; after roughly 40 hours of failures the delivery is set aside as a dead letter, which we have to release for you. Ask, if an outage has left a backlog of them. GET /v1/webhooks/deliveries lists them, newest first, with status, attempts and lastError, filtered by status, companyId, from and to — the view to give your support team when a customer says nothing arrived.

Treat the webhook as a nudge, not as the document. It tells you a documentId exists and is ready — fetch the document itself over the API.

A 2xx answer to message.inbound.received is taken as delivery: the document becomes CUSTOMER_DELIVERED, exactly as if you had acknowledged it. So answer only once the documentId is stored somewhere durable on your side. A call you did not answer leaves the document waiting.

Taking the document

# what is waiting
curl -sS -H "X-API-Key: $KEY" \
  "https://api.businessgateway.se/v1/messages?delivery=pending"

# the XML itself
curl -sS -H "X-API-Key: $KEY" \
  "https://api.businessgateway.se/v1/messages/$ID/payload"

# tell us you have it
curl -sS -X POST -H "X-API-Key: $KEY" \
  "https://api.businessgateway.se/v1/messages/$ID/ack"

delivery=pending is every inbound document that passed (or needed no) validation and has not been handed over yet, whichever delivery status it is in. Filtering on one status instead misses documents for companies with no webhook or SFTP login, which stay VALID.

Acknowledging is what closes the loop. It marks the document delivered to you and cancels any webhook attempt still queued for it. Until you ack, the document keeps showing as waiting — which is exactly what you want if your own system crashed halfway through processing it. A 2xx to the message.inbound.received webhook, or the document being written to the company's SFTP in/, counts as delivery too.

Ack is safe to repeat; acking something already delivered changes nothing.

StatusMeaning
AS4_RECEIVEDTaken off the network, not yet checked
BUSINESS_VALIDATINGBeing validated
VALIDPassed validation, ready for you
UNVALIDATEDReady for you, accepted without validation because the document type has no rules to check
CUSTOMER_DELIVERY_QUEUEDReady, and being delivered to a webhook or SFTP directory
CUSTOMER_DELIVERY_RETRY / CUSTOMER_DELIVERY_FAILEDWe are retrying, or have given up, delivering it onward. Still fetchable and ackable
CUSTOMER_DELIVEREDDelivered — acknowledged, answered 2xx on the webhook, or written to SFTP
BUSINESS_INVALIDFailed validation. The sender is told so in its recipient status
VALIDATION_UNAVAILABLE / VALIDATION_ERRORCould not be validated — no rules were available, the checker stayed down, or its answer could not be read. Terminal: the sender is told the document was not delivered

Trying it in a sandbox

A company created with sandbox: true (see Onboarding) can be sent a simulated inbound document, which then runs the real receiving path — validation, storage, webhooks, delivery=pending, ack:

curl -sS -X POST "https://api.businessgateway.se/v1/companies/$COMPANY_ID/test-inbound" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{
    "receiver": "0007:5561234567",
    "sender": "0007:5569876543",
    "type": "invoice",
    "payload": "<?xml version=\"1.0\"?>…"
  }'

receiver must be one of the sandbox company's own participants. On any other company the call is 409 NOT_SANDBOX. It is rate limited per account.

The event stream

GET /v1/stream is a WebSocket carrying the same events as they happen, plus participant status changes. It is useful for a live dashboard; it is not a substitute for webhooks, because a socket that was not connected missed what happened while it was gone. Get a ticket from POST /v1/stream-tickets first.

If a document cannot be delivered

A document addressed to a participant that is not registered here, or not capable of the document type, is refused at the edge — the sending access point learns it failed, and your customer never sees it. That is the network working as designed, but it does mean a missing RECEIVE capability looks, from the outside, like the business not existing. Check capabilities first when a customer says an expected invoice never arrived.

On this page