Skip to content

Sending & webhooks

Send invoices on behalf of your customers, and keep your UI in sync with lifecycle webhooks.

Sending as a customer

Send an invoice on a customer's behalf with the standard POST /v1/invoices endpoint — just add a sender that points to one of your customers. Identify them by their getpeppr legalEntityId or by your own externalSubTenantId.

  • Authoritative seller — the supplier identity (name, country, Peppol ID) is taken from the customer's verified legal entity. Any from in the payload is accepted but stripped (no error returned), so you can't accidentally mis-state the sender.
  • Send gate — the customer must be active on production (verified, attested, and registered on the network), or verified in sandbox. Otherwise the send is rejected with 422.
  • VAT identifier — invoices that carry VAT (including 0% and tax-exempt) require the sending customer to hold a registered VAT identifier. getpeppr registers it automatically when verification proves VAT registration; a 422 mentioning "no VAT identifier registered" means the entity needs a backfill — see Network identifiers and VAT.
  • Requires a master key with the legal_entities:send_as scope.
Single-tenant sending is unchanged. A standard key behaves exactly as before — the sender field is simply ignored. Send-as activates only for a master key.
import { Peppol } from "@getpeppr/sdk";

const peppol = new Peppol({ apiKey: "sk_live_your_master_key" });

// Identify the customer by your externalSubTenantId…
await peppol.invoices.send({
  number: "INV-2026-001",
  sender: { externalSubTenantId: "customer_8412" },
  to: {
    name: "Riverside Clinic",
    peppolId: "0208:0685660237",
    street: "Rue de la Loi 16",
    city: "Brussels",
    postalCode: "1000",
    country: "BE",
  },
  lines: [{ description: "Consultation", quantity: 1, unitPrice: 90, vatRate: 0 }],
});

// …or by the getpeppr legalEntityId:
// sender: { legalEntityId: "7c9a1b34-2d5e-4f60-8a1b-9c2d3e4f5a6b" }

Lifecycle webhooks

getpeppr pushes a webhook on each meaningful customer transition, so your platform UI stays in sync without polling. They ride the same delivery channel as your other webhooks — HMAC-signed, with automatic retries. See Webhooks for signature verification and retry behaviour.

Events

  • legal_entity.registered — a customer reached a verified or active state (status tells you which).
  • legal_entity.verification_failed — registry mismatch or not found.
  • legal_entity.awaiting_authz — an attestation request is awaiting the customer.
  • legal_entity.registration_failed — sandbox only: the customer's identity verified, but the network registration failed — the entity is not sendable. reason tells you why.
Intermediate states (attested, provisioning) are not pushed as separate events — going live is signalled by legal_entity.registered with status: "active". Poll GET /v1/legal-entities/:id if you need the fine-grained step.

Payload

Each event's data carries the fields below. peppolId is the customer's scheme and identifier joined by a colon (or null if not yet set).

  • subTenantId — your externalSubTenantId for this customer. You set this at creation, so it's present for sub-tenants you created via the API.
  • legalEntityId — the getpeppr legal entity id.
  • peppolId — the customer's Peppol identifier as "scheme:value", or null.
  • status — one of verified, verification_failed, registration_failed, awaiting_authz, active.
  • environmentsandbox or production.
  • reason — on failures: name_mismatch or not_found (verification), already_registered, invalid_format or provider_error (registration).
  • occurredAt — ISO 8601 timestamp of the transition.
Map each event to your customer record by subTenantId — it's the same reference you supplied at creation, so no extra lookup is needed.
legal_entity.registered
{
  "id": "evt_1a2b3c",
  "type": "legal_entity.registered",
  "createdAt": "2026-06-01T10:05:00.000Z",
  "data": {
    "subTenantId": "customer_8412",
    "legalEntityId": "7c9a1b34-2d5e-4f60-8a1b-9c2d3e4f5a6b",
    "peppolId": "GB:CRN:12345678",
    "status": "active",
    "environment": "production",
    "occurredAt": "2026-06-01T10:05:00.000Z"
  }
}