Skip to content
Back to News
Changelog

SDK 6.0.0 types a customer switching from another Peppol provider

sdkplatform

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.