Skip to content

Webhooks

Receive real-time notifications when invoices are delivered, accepted, rejected, or when new invoices arrive.

Setup

Configure your webhook endpoint in the getpeppr console under Settings → Webhooks. getpeppr will send a POST request to your endpoint for each event.

Requirements

  • Your endpoint must accept POST requests with application/json body
  • Respond with a 2xx status code within 5 seconds
  • Use HTTPS in production

Event Types

Event Description
invoice.sent Invoice successfully delivered to recipient's access point
invoice.accepted Recipient accepted the invoice
invoice.refused Recipient rejected the invoice
invoice.error Delivery failed (final state)
invoice.registered Cleared by tax authority (e.g., KSA, PT)
invoice.received Receipt acknowledged by recipient
invoice.paid Payment confirmed by recipient
test.ping Test event sent during endpoint setup
* Wildcard — subscribes to all event types

Handler Example

Here's a complete webhook handler using the SDK types. The WebhookEvent type ensures type safety for all event payloads.

import { webhooks } from "@getpeppr/sdk";
import type { WebhookEvent } from "@getpeppr/sdk";

// In your Express / Hono / Fastify route handler
app.post("/webhooks/peppol", async (req, res) => {
  // 1. Verify the signature (critical for security)
  let event: WebhookEvent;
  try {
    event = await webhooks.constructEvent(
      req.body,              // raw body string (NOT parsed JSON)
      req.headers["getpeppr-signature"] as string,
      "whsec_your_secret"    // from dashboard
    );
  } catch (err) {
    console.error("Signature verification failed:", err);
    return res.status(400).send("Invalid signature");
  }

  // 2. Handle the event
  switch (event.type) {
    case "invoice.sent":
      console.log(`Invoice ${event.data.invoiceId} delivered!`);
      break;

    case "invoice.accepted":
      console.log(`Invoice ${event.data.invoiceId} accepted!`);
      break;

    case "invoice.refused":
      console.error(`Invoice ${event.data.invoiceId} refused`);
      break;

    case "invoice.error":
      console.error(`Invoice ${event.data.invoiceId} delivery failed`);
      break;

    case "invoice.paid":
      console.log(`Invoice ${event.data.invoiceId} paid!`);
      break;
  }

  // 3. Always respond quickly — process async if needed
  res.status(200).send("ok");
});

// Event types:
// "invoice.sent"       — delivered to recipient's access point
// "invoice.accepted"   — recipient accepted the invoice
// "invoice.refused"    — recipient rejected the invoice
// "invoice.error"      — delivery failed (final state)
// "invoice.registered" — cleared by tax authority
// "invoice.received"   — receipt acknowledged by recipient
// "invoice.paid"       — payment confirmed by recipient
// "test.ping"          — test event for setup verification

Payload Format

Every webhook request contains a JSON body with the following structure:

  • id — unique event ID (for deduplication)
  • type — event type string
  • data — event-specific payload
  • createdAt — ISO 8601 timestamp
Webhook Payload
{
  "id": "evt_abc123def456",
  "type": "invoice.sent",
  "data": {
    "invoiceId": "12345",
    "invoiceNumber": "INV-2026-001",
    "environment": "sandbox"
  },
  "createdAt": "2026-03-01T10:05:00.000Z"
}

Security

Webhook requests include an Getpeppr-Signature header that you should verify to ensure the request is from getpeppr and not a third party.

Best Practices

  • Verify the signature — use await webhooks.constructEvent() from @getpeppr/sdk to verify the Getpeppr-Signature HMAC-SHA256 header
  • Deduplicate — use event.id to detect and ignore duplicate deliveries
  • Respond quickly — process asynchronously and return 200 immediately
  • Handle retries — getpeppr retries failed deliveries with exponential backoff
Always verify the Getpeppr-Signature header in production using await webhooks.constructEvent() from @getpeppr/sdk. Without verification, an attacker could send fake events to your endpoint.