Skip to main content

Webhook Payloads

Every webhook delivery is a POST request with a JSON body containing the event details and enriched resource data.

Request headers

HeaderDescriptionExample
Content-TypeAlways application/jsonapplication/json
X-Webhook-IDUnique delivery ID (changes on each retry)f47ac10b-58cc-4372-a567-0e02b2c3d479
X-Webhook-Event-IDUnique event ID (same across retries, use for deduplication)evt_80e0a4bcb0534c21967f50c54602433e
X-Webhook-TimestampUnix timestamp in seconds1712000382
X-Webhook-SignatureHMAC-SHA256 signature (hex-encoded)a1b2c3d4e5f6...
X-Webhook-EventEvent typeinvoice.paid
User-AgentIdentifies the senderOnlineInvoiceMaker-Webhooks/1.0

Payload structure

All events share the same top-level structure:

{
"id": "evt_80e0a4bcb0534c21967f50c54602433e",
"type": "invoice.created",
"created_at": "2026-04-01T10:30:00.000Z",
"business_id": "cb21efb1-fa40-434f-a1d3-e17c0bdb9aa6",
"data": {
// Event-specific data (see below)
}
}
FieldTypeDescription
idstringUnique event ID prefixed with evt_. Use for deduplication
typestringThe event type
created_atstring (ISO 8601)When the event occurred
business_idstring (UUID)The business that owns the event
dataobjectFull enriched data for the resource

Invoice event payload

Events: invoice.created, invoice.updated, invoice.sent, invoice.paid, invoice.overdue, invoice.cancelled, invoice.deleted

{
"id": "evt_80e0a4bcb0534c21967f50c54602433e",
"type": "invoice.paid",
"created_at": "2026-04-01T12:00:00.000Z",
"business_id": "cb21efb1-fa40-434f-a1d3-e17c0bdb9aa6",
"data": {
"id": "dad9a150-8bb6-4764-9a22-ac8b83734e24",
"invoice_number": "INV-00001",
"total": 3109.88,
"status": "paid",
"previous_status": "sent",
"invoice": {
"id": "dad9a150-8bb6-4764-9a22-ac8b83734e24",
"invoice_number": "INV-00001",
"status": "paid",
"issue_date": "2026-04-01",
"due_date": "2026-05-01",
"currency": "USD",
"subtotal": 2859.88,
"tax_amount": 250.00,
"discount_amount": 0,
"total": 3109.88,
"amount_paid": 3109.88,
"notes": "Thank you for your business!",
"terms": null,
"template_type": "modern",
"created_at": "2026-04-01T10:30:00.000Z",
"updated_at": "2026-04-01T12:00:00.000Z"
},
"items": [
{
"id": "a1b2c3d4-...",
"description": "Website Design",
"quantity": 1,
"unit_price": 2500,
"tax_rate": 10,
"tax_amount": 250.00,
"amount": 2750.00,
"product_id": null
},
{
"id": "e5f6a7b8-...",
"description": "Hosting (12 months)",
"quantity": 12,
"unit_price": 29.99,
"tax_rate": 0,
"tax_amount": 0,
"amount": 359.88,
"product_id": null
}
],
"customer": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corp",
"email": "billing@acme.com",
"phone": "+1-555-0100",
"address": "123 Business Ave",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"country": "US",
"tax_id": "US-12345678"
},
"business": {
"id": "cb21efb1-fa40-434f-a1d3-e17c0bdb9aa6",
"name": "My Company LLC",
"business_type": "LLC",
"phone": "+1-555-0200",
"address": "456 Commerce St",
"country": "US",
"currency": "USD",
"tax_id": "EIN-98765432",
"logo_url": "https://...",
"website": "https://mycompany.com"
}
}
}

Quotation event payload

Events: quotation.created, quotation.updated, quotation.sent, quotation.accepted, quotation.rejected, quotation.expired, quotation.converted

{
"id": "evt_...",
"type": "quotation.accepted",
"created_at": "2026-04-01T14:00:00.000Z",
"business_id": "cb21efb1-...",
"data": {
"id": "7a8b9c0d-...",
"quotation_number": "QT-00001",
"total": 5900.00,
"status": "accepted",
"quotation": {
"id": "7a8b9c0d-...",
"quotation_number": "QT-00001",
"status": "accepted",
"issue_date": "2026-04-01",
"valid_until": "2026-05-01",
"currency": "USD",
"subtotal": 5400.00,
"tax_amount": 500.00,
"discount_amount": 100.00,
"total": 5900.00,
"notes": null,
"terms": null,
"template_type": "modern",
"created_at": "2026-04-01T10:30:00.000Z",
"updated_at": "2026-04-01T14:00:00.000Z"
},
"items": [
{
"id": "...",
"description": "Full Brand Identity Package",
"quantity": 1,
"unit_price": 5000,
"tax_rate": 10,
"tax_amount": 500.00,
"amount": 5500.00,
"product_id": null
}
],
"customer": { ... },
"business": { ... }
}
}

Customer event payload

Events: customer.created, customer.updated, customer.deleted

{
"id": "evt_...",
"type": "customer.created",
"created_at": "2026-04-01T10:00:00.000Z",
"business_id": "cb21efb1-...",
"data": {
"id": "550e8400-...",
"name": "Acme Corp",
"email": "billing@acme.com",
"customer": {
"id": "550e8400-...",
"name": "Acme Corp",
"email": "billing@acme.com",
"phone": "+1-555-0100",
"address": "123 Business Ave",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"country": "US",
"tax_id": "US-12345678",
"notes": null
},
"business": { ... }
}
}

Recurring invoice event payload

Events: recurring_invoice.generated, recurring_invoice.failed, recurring_invoice.completed

{
"id": "evt_...",
"type": "recurring_invoice.generated",
"created_at": "2026-04-01T00:00:00.000Z",
"business_id": "cb21efb1-...",
"data": {
"recurring_invoice_id": "...",
"invoice_id": "...",
"invoice_number": "INV-00042",
"total": 1500.00,
"occurrence_number": 3,
"recurring_invoice": {
"id": "...",
"name": "Monthly Retainer - Acme",
"frequency": "monthly",
"status": "active",
"start_date": "2026-01-01",
"next_run_date": "2026-05-01",
"end_date": null,
"occurrences_count": 3,
"max_occurrences": 12,
"currency": "USD",
"auto_send": true,
"created_at": "2025-12-15T10:00:00.000Z"
},
"generated_invoice": {
"id": "...",
"invoice_number": "INV-00042",
"status": "sent",
"issue_date": "2026-04-01",
"due_date": "2026-05-01",
"currency": "USD",
"subtotal": 1500.00,
"tax_amount": 0,
"total": 1500.00,
"items": [
{
"description": "Monthly Retainer",
"quantity": 1,
"unit_price": 1500
}
]
},
"customer": { ... },
"business": { ... }
}
}

Deduplication

Use the id field (event ID) to deduplicate events. The same event may be delivered more than once due to retries. The event ID remains the same across all delivery attempts, while the X-Webhook-ID header changes per attempt.

const processedEvents = new Set();

app.post('/webhooks', (req, res) => {
const eventId = req.body.id;

if (processedEvents.has(eventId)) {
return res.status(200).send('Already processed');
}

processedEvents.add(eventId);
// Process the event...

res.status(200).send('OK');
});
Production deduplication

In production, store processed event IDs in your database (e.g., a processed_webhook_events table) instead of an in-memory Set.