All Docs
FeaturesCalmony PayUpdated March 15, 2026

Webhook Event Fix: invoice.finalized Now Fires Correctly on Finalize

Webhook Event Fix: invoice.finalized Now Fires Correctly on Finalize

Release: v1.0.96

What Changed

Prior to this release, calling POST /v1/invoices/[id]/finalize — the endpoint that transitions a draft invoice to open status — was emitting the wrong webhook event. Instead of firing invoice.finalized, it was emitting invoice.created.

Why This Matters

This was a spec deviation with two concrete consequences:

  1. Double-emit of invoice.created — The invoice.created event is already fired when a draft invoice is first created via POST /v1/invoices. Firing it again on finalize caused webhook consumers to receive a duplicate creation signal, which could trigger unintended side-effects such as double-provisioning, duplicate notifications, or incorrect billing state transitions.

  2. Suppressed invoice.finalized event — Downstream systems and webhook consumers that listen for invoice.finalized to know when an invoice becomes payable never received this event. Any logic gated on an invoice moving from draft → open (e.g. sending a payment link, updating an accounts receivable ledger, or triggering a dunning workflow) would silently fail.

Correct Behaviour (from v1.0.96)

ActionEndpointWebhook Event
Create draft invoicePOST /v1/invoicesinvoice.created
Finalize draft invoicePOST /v1/invoices/[id]/finalizeinvoice.finalized

Action Required

If you have webhook consumers that were listening to invoice.created as a signal that an invoice became payable (i.e. open), you should update them to listen for invoice.finalized instead.

The invoice.created event should only be treated as a signal that a new draft invoice exists — not that it is ready for payment.

Example: Updating a Webhook Handler

// Before (incorrect — relying on invoice.created for payable invoices)
if (event.type === 'invoice.created') {
  await sendPaymentLink(event.data.object);
}

// After (correct — use invoice.finalized for payable invoices)
if (event.type === 'invoice.finalized') {
  await sendPaymentLink(event.data.object);
}

// invoice.created should now only handle draft invoice creation
if (event.type === 'invoice.created') {
  await recordDraftInvoice(event.data.object);
}

Affected Webhook Event Types

  • invoice.created — Fires when a new draft invoice is created via POST /v1/invoices. No change to this behaviour.
  • invoice.finalized — Now correctly fires when a draft invoice is transitioned to open via POST /v1/invoices/[id]/finalize.