Webhooks
Receive signed, real-time events for invoices, payments, stock, purchases and every other change in a company.
Overview
Webhooks let your systems react to changes in Corebill without polling. When a subscribed event happens, Corebill sends an HTTP POST with a JSON body to your endpoint, signed with the destination secret.
Events fire the same way whether the change came from the dashboard, the REST API, an automation, Stripe or the AI Agent. Destinations are configured per company in Settings > Developers > Webhooks: URL, events, custom headers, signing secret and the delivery log.
Events of optional modules (Vendors, Inventory, Purchases, Cash, Point of sale, Work orders, Projects, Expenses, Subscriptions) are only available while the module is enabled in the company.
Available Events
Customers
| Event | When it fires | Automations |
|---|---|---|
customer.created | A new customer was added from the app, the API, an import or the AI agent. | Yes |
customer.updated | Any field of a customer changed. | Yes |
customer.deleted | A customer was deleted. | No |
Quotes
| Event | When it fires | Automations |
|---|---|---|
quote.created | A quote or a new version of a quote was created. | Yes |
quote.updated | The lines, amounts or details of a quote changed. | Yes |
quote.sent | A quote was sent to the customer. | Yes |
quote.approved | The customer or your team approved a quote. | Yes |
quote.rejected | The customer or your team rejected a quote. | Yes |
quote.expired | A quote passed its valid until date without an answer. | Yes |
quote.converted | An invoice was created from a quote. | Yes |
quote.deleted | A quote was deleted. | No |
Invoices
| Event | When it fires | Automations |
|---|---|---|
invoice.created | A draft invoice was created. | Yes |
invoice.updated | The lines, dates or details of an invoice changed. | Yes |
invoice.issued | An invoice left draft and got its final number. | Yes |
invoice.sent | An invoice was sent to the customer. | Yes |
invoice.payment_recorded | A payment was recorded and the invoice still has a balance due. | Yes |
invoice.paid | An invoice was paid in full. | Yes |
invoice.payment_updated | A recorded payment of an invoice was edited. | No |
invoice.payment_deleted | A recorded payment of an invoice was deleted. | No |
invoice.overdue | An invoice reached its due date with a balance due. | Yes |
invoice.cancelled | An issued invoice was voided. | Yes |
invoice.marked_uncollectible | The balance due of an invoice was written off. | Yes |
invoice.refunded | A refund was recorded on an invoice. | Yes |
invoice.deleted | A draft invoice was deleted. | No |
Payments
| Event | When it fires | Automations |
|---|---|---|
payment.received | Money came in for an invoice: manual, Stripe, point of sale or API. | Yes |
payment.failed | A card payment through Stripe was declined. | Yes |
Services and products
| Event | When it fires | Automations |
|---|---|---|
item.created | A new item was added to the catalog. | Yes |
item.updated | The price or details of a catalog item changed. | Yes |
item.deleted | A catalog item was deleted. | No |
Subscriptions
Requires the Subscriptions module.
| Event | When it fires | Automations |
|---|---|---|
subscription.canceled | A Stripe subscription ended. | Yes |
subscription.cancel_scheduled | A Stripe subscription will end at the close of its period. | Yes |
subscription.plan_changed | A Stripe subscription moved to another plan or price. | Yes |
Projects and tasks
Requires the Projects module.
| Event | When it fires | Automations |
|---|---|---|
project.created | A new project was created. | Yes |
project.updated | The status or details of a project changed. | Yes |
project.deleted | A project was deleted. | No |
task.created | A task was added to a project. | Yes |
task.updated | The status, assignee or details of a task changed. | Yes |
task.deleted | A task was deleted. | No |
Expenses
Requires the Expenses module.
| Event | When it fires | Automations |
|---|---|---|
expense.created | An expense was recorded, including Stripe fees and recurring expenses. | Yes |
expense.updated | The amount or details of an expense changed. | Yes |
expense.deleted | An expense was deleted. | No |
Vendors
Requires the Vendors module.
| Event | When it fires | Automations |
|---|---|---|
vendor.created | A new vendor was added. | Yes |
vendor.updated | The details, terms or bank data of a vendor changed. | Yes |
vendor.deleted | A vendor was deleted. | No |
Inventory
Requires the Inventory module.
| Event | When it fires | Automations |
|---|---|---|
stock.adjusted | Stock went in or out with an adjustment (count, damage, gift...) or its reversal. | Yes |
stock.transferred | Products moved from one warehouse to another. | Yes |
stock.low | A product reached its reorder point after a sale or stock out. | Yes |
Purchases
Requires the Purchases module.
| Event | When it fires | Automations |
|---|---|---|
purchase_order.created | A purchase order was created as a draft. | Yes |
purchase_order.approved | A purchase order was approved or confirmed. | Yes |
purchase_order.received | Goods of a purchase order were received, in part or in full. | Yes |
bill.registered | A vendor bill was registered and added to accounts payable. | Yes |
bill.payment_recorded | A payment to a vendor was recorded. | Yes |
bill.paid | A vendor bill was paid in full. | Yes |
bill.cancelled | A vendor bill was cancelled. | Yes |
Cash
Requires the Cash module.
| Event | When it fires | Automations |
|---|---|---|
cash_session.opened | A cash register session was opened with its opening amount. | Yes |
cash_session.closed | A cash register session was closed with its count and difference. | Yes |
cash_movement.created | Cash went in or out of a register manually. | Yes |
Point of sale
Requires the Point of sale module.
| Event | When it fires | Automations |
|---|---|---|
pos_sale.completed | A point of sale ticket was charged. | Yes |
pos_sale.voided | A point of sale ticket was voided and refunded. | Yes |
Work orders
Requires the Work orders module.
| Event | When it fires | Automations |
|---|---|---|
work_order.created | A new work order was opened. | Yes |
work_order.status_changed | A work order moved to pending, in progress, ready or delivered. | Yes |
work_order.delivered | A work order was delivered to the customer. | Yes |
work_order.payment_recorded | A payment or down payment of a work order was recorded. | Yes |
Payload
id is the event id (the same on every retry), event the event type and data the resource. Core resources (customers, quotes, invoices, items, expenses) use the same shape as the REST API. metadata carries the custom fields of customers, quotes and invoices. Test events include "test": true.
Headers
| Header | Description |
|---|---|
Corebill-Signature | t=. The signature is HMAC-SHA256 (hex) of with your secret. After a secret rotation a second v1 signed with the previous secret is included for 24 hours |
X-Webhook-Signature | Legacy: HMAC-SHA256 (hex) of the raw body. Kept for compatibility; prefer Corebill-Signature |
X-Webhook-Event | Event type (e.g. invoice.paid) |
X-Webhook-Id | The destination ID (wh_...) |
X-Event-Id | The event ID. Use it to deduplicate |
X-Webhook-Delivery | The delivery ID (new on every attempt) |
X-Webhook-Attempt | Attempt number (1 to 5) |
X-Webhook-Timestamp | Same timestamp used in the signature |
User-Agent | Corebill-Webhooks/1.0 |
| Custom headers | The headers you configure on the destination (for example an API token of your system) |
Verifying the Signature
Always verify the signature before trusting a webhook. Use the raw request body (do not parse and re-stringify it), compare in constant time and reject timestamps older than 5 minutes to block replays.
Responding
Return a 2xx status code within 10 seconds. Anything else, a redirect or a timeout counts as a failure. Keep handlers fast: store the event, answer 200 and process it in the background.
Retries
| Attempt | When |
|---|---|
| 1 | Immediately |
| 2 | 1 minute after the first failure |
| 3 | 5 minutes later |
| 4 | 30 minutes later |
| 5 | 2 hours later |
After 5 failed attempts the delivery stays as failed. You can resend any delivery from the destination log; a resend replaces a pending automatic retry.
Idempotency
The same event can arrive more than once (retries, manual resends). Use the envelope id (or the X-Event-Id header) as an idempotency key: if you already processed evt_..., ignore it.
Secret Rotation
Rotate the signing secret from the destination detail. For the next 24 hours every event carries two v1 signatures (new and previous secret), so you can deploy the new secret without losing events. Accept the event if any v1 matches.
Testing
- Open Settings > Developers > Webhooks and pick a destination
- Click Send test event and choose any event of the catalog
- Corebill sends the example payload with
"test": trueand shows the status code and response time. Test events are not retried
The delivery log keeps every attempt with the payload, the headers sent (custom header values are hidden), the response and the response headers.
Building the endpoint on your machine? Run corebill listen --forward-to localhost:3000/webhooks/corebill and corebill trigger invoice.paid: events reach your local server signed like real webhooks, no public URL needed. See the Events page.