@getpeppr/sdk
v1.1.3
Published
Send Peppol e-invoices in under 20 lines of code
Maintainers
Readme
@getpeppr/sdk
Send Peppol e-invoices in under 20 lines of code.
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
const result = await peppol.invoices.send({
number: "INV-2026-001",
to: {
name: "Acme Corp",
peppolId: "0208:BE9876543210",
street: "123 Business Ave",
city: "Brussels",
postalCode: "1000",
country: "BE",
},
lines: [
{ description: "Consulting services", quantity: 10, unitPrice: 150, vatRate: 21 },
],
});
console.log(`Invoice sent! ID: ${result.id}`);Installation
npm install @getpeppr/sdkFeatures
- Stripe-like DX -- JSON in, Peppol out
- Full Peppol BIS 3.0 compliance -- UBL 2.1 XML generation handled for you
- All invoice types -- standard (380), credit note (381), debit (383), prepayment (386), self-billed (389)
- Client-side + server-side validation -- XSD and Schematron rules
- Peppol Directory lookup -- verify participants before sending
- Webhook signature verification -- HMAC-SHA256 with replay protection
- Batch send with concurrency control
- Built-in retry with exponential backoff and rate limit handling
- Async pagination -- iterate over all invoices, events, contacts
- TypeScript-first, zero dependencies
Quick Examples
Credit note
const creditNote = await peppol.invoices.send({
number: "CN-2026-001",
isCreditNote: true,
invoiceReference: "INV-2026-001",
to: {
name: "Acme Corp",
peppolId: "0208:BE9876543210",
street: "123 Business Ave",
city: "Brussels",
postalCode: "1000",
country: "BE",
},
lines: [
{ description: "Refund for consulting services", quantity: 2, unitPrice: 150, vatRate: 21 },
],
});Webhook verification
import { webhooks } from "@getpeppr/sdk";
app.post("/webhooks/peppol", async (req, res) => {
try {
const event = await webhooks.constructEvent(
req.body, // Raw body string (NOT parsed JSON)
req.headers["getpeppr-signature"], // Getpeppr-Signature header
"whsec_your_webhook_secret", // Your webhook secret
);
switch (event.type) {
case "invoice.accepted":
console.log("Invoice accepted:", event.data);
break;
case "invoice.received":
console.log("New invoice received:", event.data);
break;
}
res.sendStatus(200);
} catch (err) {
res.status(400).send("Webhook verification failed");
}
});Directory lookup
const entry = await peppol.directory.lookup("0208:BE0123456789");
console.log(entry.name); // "My Company NV"
console.log(entry.country); // "BE"
console.log(entry.capabilities); // ["invoice", "credit_note"]Batch send
const invoices = [invoice1, invoice2, invoice3];
const result = await peppol.invoices.sendBatch(invoices, {
concurrency: 5,
stopOnError: false,
});
console.log(`${result.succeeded.length} sent, ${result.failed.length} failed`);Validation
const result = peppol.validate({
number: "INV-001",
to: {
name: "Acme",
peppolId: "0208:BE9876543210",
street: "123 Business Ave",
city: "Brussels",
postalCode: "1000",
country: "BE",
},
lines: [{ description: "Item", quantity: 1, unitPrice: 100, vatRate: 21 }],
});
if (!result.valid) {
for (const err of result.errors) {
console.log(`${err.field}: ${err.message}`);
}
}Client-side validation runs instantly offline. It checks Peppol BIS 3.0 rules, field formats, and country-specific requirements before the invoice reaches the network.
Export PDF / XML
import { writeFileSync } from "fs";
const pdf = await peppol.invoices.getAs("inv-abc123", "pdf");
writeFileSync("invoice.pdf", Buffer.from(pdf));
const xml = await peppol.invoices.getAs("inv-abc123", "xml.ubl.invoice.bis3");
writeFileSync("invoice.xml", Buffer.from(xml));Configuration
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({
apiKey: "sk_live_...", // Required. Starts with sk_sandbox_ or sk_live_
baseUrl: "https://...", // Default: https://api.getpeppr.dev/v1
timeout: 30000, // Request timeout in ms (default: 30s)
retry: {
maxRetries: 3, // Max retry attempts (default: 3)
initialDelayMs: 500, // Initial retry delay (default: 500ms)
maxDelayMs: 30000, // Max retry delay (default: 30s)
},
onRequest: (entry) => {}, // Optional request hook
onResponse: (entry) => {}, // Optional response hook
});Error Handling
The SDK provides three error classes for precise error handling:
import { Peppol, PeppolError, PeppolValidationError, PeppolApiError } from "@getpeppr/sdk";
try {
await peppol.invoices.send(input);
} catch (err) {
if (err instanceof PeppolValidationError) {
// Client-side validation failed
console.log(err.validation.errors); // ValidationError[]
console.log(err.validation.warnings); // ValidationWarning[]
} else if (err instanceof PeppolApiError) {
// API returned an error
console.log(err.statusCode); // HTTP status code
console.log(err.responseBody); // Raw response body
console.log(err.retryAfterMs); // Retry-After delay (on 429)
} else if (err instanceof PeppolError) {
// General SDK error (invalid config, timeout, etc.)
console.log(err.message);
}
}Transient errors (429, 500, 502, 503, 504) are automatically retried with exponential backoff and jitter. Rate limit responses (429) respect the Retry-After header.
Webhook Events
| Event | Description |
|-------|-------------|
| invoice.sent | Invoice sent to the Peppol network |
| invoice.accepted | Recipient accepted the invoice |
| invoice.refused | Recipient refused the invoice |
| invoice.error | Delivery error |
| invoice.registered | Invoice registered by recipient |
| test.ping | Test event for endpoint verification |
Additional event types (invoice.paid, invoice.closed, invoice.received, creditnote.sent, creditnote.received) are defined in the SDK types for forward compatibility but are not yet emitted by the gateway.
Links
License
MIT
