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
POSTrequests withapplication/jsonbody - Respond with a
2xxstatus 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 verificationPayload Format
Every webhook request contains a JSON body with the following structure:
id— unique event ID (for deduplication)type— event type stringdata— event-specific payloadcreatedAt— ISO 8601 timestamp
{
"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/sdkto verify theGetpeppr-SignatureHMAC-SHA256 header - Deduplicate — use
event.idto detect and ignore duplicate deliveries - Respond quickly — process asynchronously and return
200immediately - 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.