Webhook System
Outbound webhooks let external services react to events happening in Invoicerr (invoice paid, quote signed, client created, etc.).
Dispatch mechanism
Services call webhookDispatcher.dispatch(WebhookEvent.SOME_EVENT, payload) (see backend/src/modules/webhooks/webhook-dispatcher.service.ts). The dispatcher:
- Looks up webhooks configured for that event and the relevant company.
- Calls the webhooks service
send()method, which routes to the configured driver — generic HTTP, Slack, Discord, Microsoft Teams. - Signs the payload with HMAC-SHA256 using the webhook's secret.
Available events
The WebhookEvent enum (backend/prisma/schema.prisma) carries exactly 18 members today, every one
of which has a real dispatch call site in backend/src (backend/src/modules/webhooks/webhook-event.spec.ts
pins the exact list; webhook-event-emitters.spec.ts re-derives "has a real emitter" from a live grep
on every test run, so the enum cannot silently regrow dead members). It used to carry far more: two
purges (20260903000000_generic_document_webhook_events, 51 members; 20260903200000_purge_dead_webhook_events,
79 more) removed every member that had accumulated since the very first webhook module without a
real emitter ever being wired for it — per-type QUOTE_*/INVOICE_*/PAYMENT_*/RECEIPT_*/
SIGNATURE_*, RECURRING_INVOICE_*, PLUGIN_*, and about a dozen other families. Read either
migration's own header for the per-family evidence if you are wondering whether something that used
to exist should come back.
- Documents (generic across every document type — invoice, quote, credit note, purchase order,
...):
DOCUMENT_CREATED,DOCUMENT_SENT,DOCUMENT_SEND_FAILED,DOCUMENT_AUTHORITY_EVENT,DOCUMENT_DELETED,DOCUMENT_SETTLED,DOCUMENT_CANCELLED,DOCUMENT_SIGNED. - Clients:
CLIENT_CREATED,CLIENT_UPDATED,CLIENT_DELETED,CLIENT_SEARCHED. - Company:
COMPANY_CREATED,COMPANY_UPDATED,COMPANY_EMAIL_TEMPLATE_UPDATED. - Webhook subscriptions themselves:
WEBHOOK_CREATED,WEBHOOK_UPDATED,WEBHOOK_DELETED.
The settings screen (frontend/.../settings/_components/webhooks.settings.tsx) never hardcodes this
list — it renders whatever GET /api/webhooks/options returns, so it can never drift from the enum.
Adding a new webhook event
- Add the new value to the
WebhookEventenum inbackend/prisma/schema.prisma. - Run
npx prisma migrate devto regenerate Prisma's types and create the migration — Postgres cannotALTER TYPE ... ADD VALUEinside the same transaction some ORMs batch migrations in, but Prisma's own generated SQL handles a plain addition (only removing a value needs the rebuild-the-type dance either purge migration above demonstrates). - Call
webhookDispatcher.dispatch(WebhookEvent.YOUR_NEW_EVENT, { ...payload })— or, for a document event, theDocumentWebhookEmitter.dispatchit mirrors (documents/queue/document-webhooks.ts) — from the relevant service, at the point where the underlying state change happens. A value with no real call site failswebhook-event-emitters.spec.tson the nextnpm test.