Invoice Responses
Tell the sender what happened to an invoice you received — accepted, rejected, under query, paid — over the Peppol network.
What an Invoice Response is
An Invoice Response is the Peppol document a buyer sends back to the supplier about an invoice or credit note it received: its status, the reasons for it, and what the supplier is expected to do. getpeppr sends it as a Peppol BIS Invoice Response 3.x (a UBL ApplicationResponse), validated against the Peppol rules before it leaves.
Some e-invoicing rules require receiving software to offer it. You send one with POST /v1/received-documents/{id}/responses, where id is the receivedDocumentId of the inbound.invoice.received or inbound.creditnote.received webhook. Only an invoice or a credit note can be answered: any other received document answers 422 with received_documents.response_document_unsupported.
Response codes
responseCode | Meaning | Reason required |
|---|---|---|
AB | Acknowledged — received, not processed yet | No |
IP | In process | No |
UQ | Under query | Yes |
CA | Conditionally accepted | Yes |
RE | Rejected | Yes |
AP | Accepted | No |
PD | Paid — use the PPD reason for a partial payment | No |
reasons say why the document has this status (OPStatusReason: NON, REF, LEG, REC, QUA, DEL, PRI, QTY, ITM, PAY, UNR, FIN, PPD, OTH). actions say what the supplier should do (OPStatusAction: NOA, PIN, NIN, CNF, CNP, CNA, OTH). PPD is allowed with PD only; PPD and OTH need a text. At most 10 reasons and actions together; each text up to 500 characters, note up to 1000.
Sending a response
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
// The id from the inbound.invoice.received webhook (data.receivedDocumentId)
const receivedDocumentId = "3f7c1a52-9d4e-4c1b-8a2f-6b5e0d1c7a93";
const response = await peppol.receivedDocuments.respond(
receivedDocumentId,
{
responseCode: "RE",
reasons: [{ code: "REF", text: "Purchase order number missing" }],
actions: [{ code: "NIN" }],
},
// Same key on a retry: the response is never sent twice
{ idempotencyKey: `reject-${receivedDocumentId}` },
);
console.log(response.delivery); // "submitted"The answer is 202 Accepted with delivery: "submitted": the response is on its way, and delivery is asynchronous. Pass an Idempotency-Key: a retry with the same key and body never sends a second response. Once a response has left, the same key with a different body is refused; after a refusal that sent nothing, you may correct the body and retry with the same key.
When the supplier does not accept responses
A Peppol participant receives Invoice Responses only if it advertises that document type on the network, and many suppliers do not. In that case nothing is sent and the call is refused at once with 422 and the result code received_documents.response_not_accepted_by_sender. Nothing in the request can fix it: tell the supplier by another channel. One exception we have measured: a supplier connected through our own Peppol provider that does not advertise Invoice Responses may have the response accepted without any software reading it, as with the console's test sender below.
import { Peppol, PeppolApiError } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const receivedDocumentId = "3f7c1a52-9d4e-4c1b-8a2f-6b5e0d1c7a93";
try {
await peppol.receivedDocuments.respond(receivedDocumentId, { responseCode: "AP" });
} catch (err) {
if (
err instanceof PeppolApiError &&
err.resultCode === "received_documents.response_not_accepted_by_sender"
) {
// Nothing was sent: this sender does not receive Invoice Responses.
// Tell them another way (email, portal) and record it on your side.
} else {
throw err;
}
}Following delivery
GET /v1/received-documents/{id}/responses lists the responses sent on a document, oldest first. A response refused at once is not listed — it never left.
pending— being sent, or an attempt got no answer yet: retry with the same Idempotency-Keysubmitted— accepted for transmissiondelivered— received by the supplier's access pointfailed— not delivered;failureReasonisno_recipientordelivery_failedunconfirmed— an attempt got no answer and its outcome could not be established: contact support before sending again
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const receivedDocumentId = "3f7c1a52-9d4e-4c1b-8a2f-6b5e0d1c7a93";
const responses = await peppol.receivedDocuments.listResponses(receivedDocumentId);
for (const r of responses) {
// delivery: "pending" | "submitted" | "delivered" | "failed" | "unconfirmed"
console.log(r.responseCode, r.delivery, r.failureReason ?? "");
}Who can respond
- A standard key responds to documents received by your own legal entity. A sub-tenant's document answers
404, as for every read. - A master key also responds for its sub-tenants. The sub-tenant must be able to send: the same rules as
send_asapply. - In production, responding as your own legal entity requires a verified Peppol identity, and every response passes the same commercial checks as sending an invoice (an active Platform contract, a production access that has not lapsed).
- In the sandbox, your responses travel on the Peppol test network. Test documents sent from the console's Send a test document button come from a getpeppr test sender that does not advertise Invoice Responses: a response to one is accepted, but no supplier software reads it. To test the full loop, send an invoice between two of your own sandbox legal entities: the sending one must advertise Invoice Responses, which legal entities registered through getpeppr do today. If an older sandbox entity of yours does not, ask support to update it.