npm package: @getpeppr/sdk
Description: Send Peppol e-invoices in under 20 lines of code
Author: getpeppr
Latest version: 1.1.2

# @getpeppr/sdk

Send Peppol e-invoices in under 20 lines of code.

[![npm version](https://img.shields.io/npm/v/@getpeppr/sdk.svg)](https://www.npmjs.com/package/@getpeppr/sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-first-blue.svg)](https://www.typescriptlang.org/)
[![Node](https://img.shields.io/badge/Node-%3E%3D18-green.svg)](https://nodejs.org/)

```typescript
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

```bash
npm install @getpeppr/sdk
```

## Features

- **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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

```typescript
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

- [Documentation](https://getpeppr.dev/docs)
- [Website](https://getpeppr.dev)
- [GitHub](https://github.com/zerolooplabs/getpeppr)

## License

MIT