The gateway learned this week to register a platform customer that is leaving
another Peppol access point. 6.0.0 types what it now answers. No call is
removed, but three TypeScript changes can break a build, hence a major:
AttestationResult.expiresAt becomes optional (last section), and the
LegalEntityStatus and WebhookEventType unions each gain a member
(awaiting_release, legal_entity.awaiting_release). An exhaustive switch
over either needs a new case.
A customer whose address is still held elsewhere
A Peppol address lives at one access point at a time. When your customer has
authorised production but its previous provider has not yet removed the
address, the legal entity reads awaiting_release. getpeppr checks every
minute and registers the address as soon as it is released, for up to 30
days. The status then moves on to active.
const le = await peppol.legalEntities.get(legalEntityId);
if (le.status === "awaiting_release") {
console.log(le.registrationDetail);
// { reason: "already_registered",
// heldBy: "smp.previous-provider.example",
// waitingUntil: "2026-11-01T09:30:00.000Z" }
}
heldBy is the SMP host of the access point that still holds the address. It
is absent when that host is not a plain DNS name. waitingUntil is when
getpeppr stops waiting. When the wait starts, getpeppr sends the new webhook
event legal_entity.awaiting_release, then legal_entity.registered once the
address is reachable. An endpoint subscribed to * receives the new event; an
endpoint that lists event names explicitly receives it only after you add
legal_entity.awaiting_release to its list. Production only: the sandbox has
no authorisation step.
Re-arming a failed registration
If the 30 days end, or the provider refuses the registration, the status is
provisioning_failed. Call requestAttestation() again on that entity:
getpeppr tries again for up to 30 days, with no new email and no new
confirmation from your customer. A definitive refusal by the provider ends the
new attempt earlier.
const result = await peppol.legalEntities.requestAttestation(legalEntityId, {
contactEmail: "owner@acme.example",
});
// { id: "…", externalId: "customer_8412", status: "provisioning" }
The body is validated as for a first request, so contactEmail is still
required. A re-arm acknowledges the failure: if its
legal_entity.registration_failed event had not been queued for delivery yet,
it is not sent. An event already queued may still arrive after the 202;
compare its occurredAt with the time of your re-arm.
Whether a Norwegian customer can send
legalEntities.get(), list() and create() carry vatRegistration for a
customer whose primary identifier is Norwegian (0192), and omit it for every
other country:
le.vatRegistration;
// { status: "registered", checkedAt: "2026-09-30T08:12:44.000Z",
// source: "brreg", vatNumberAdded: true }
status: "active" means the customer can receive. It can also send only when
vatNumberAdded is true: our Peppol provider refuses a Norwegian invoice
without the seller's MVA number. status is what the Brønnøysund VAT Register
said (registered, not_registered or unknown), and unknown is never a
"not registered".
expiresAt is now optional
AttestationResult.expiresAt is present when a link was emailed
(status: "awaiting_authz") and absent on a re-arm, where no link exists.
5.11.0 filled it with an empty string. If your TypeScript code passes
expiresAt where a string is required, it no longer compiles, even if it
never re-arms anything. Check status first:
if (result.status === "awaiting_authz" && result.expiresAt) {
scheduleReminder(new Date(result.expiresAt));
}
Upgrade with npm install @getpeppr/sdk@6.0.0. The full list is in the
CHANGELOG.md shipped with the package. The CLI 0.14.0 bundles it, with no
change to any command.