All Docs
FeaturesCalmony PayUpdated March 15, 2026

Fix: invoice.finalized webhook now fires correctly on finalization

Fix: invoice.finalized webhook now fires correctly on finalization

Version: v1.0.97

Overview

A spec drift was identified and resolved in the Calmony Pay invoice lifecycle. The POST /v1/invoices/{id}/finalize endpoint was emitting the wrong webhook event — this has now been corrected.

Background: Invoice lifecycle and webhooks

Calmony Pay invoices follow a two-stage creation lifecycle:

  1. Draft creationPOST /v1/invoices inserts a new invoice in draft status and emits the invoice.created webhook event.
  2. FinalizationPOST /v1/invoices/{id}/finalize transitions the invoice from draft to open (payable) status. This should emit the invoice.finalized webhook event.

The problem

Prior to v1.0.97, the finalize endpoint incorrectly dispatched invoice.created instead of invoice.finalized. This meant:

  • invoice.created fired twice for the same invoice — once on draft insertion and again on finalization.
  • invoice.finalized never fired, even though it is a registered known event type in the system.
  • Downstream webhook consumers had no reliable signal to act on an invoice becoming payable.

The fix

The finalize endpoint (src/app/api/v1/invoices/[id]/finalize/route.ts) now emits invoice.finalized as required by the Calmony Pay REST API Contract.

What you need to do

If your integration listens for webhook events related to invoice finalization, review your handlers:

ScenarioRecommended action
You listened to invoice.created to detect when an invoice became payableAdd a handler for invoice.finalized instead
You want to know when a brand-new invoice is draftedContinue listening to invoice.created — no change needed
You were subscribed to invoice.finalized but never received itYou will now start receiving it as of v1.0.97

Webhook event reference

invoice.created

Fired when a new invoice is first created as a draft.

  • Trigger: POST /v1/invoices
  • Invoice status at time of event: draft

invoice.finalized

Fired when a draft invoice is finalized and becomes payable.

  • Trigger: POST /v1/invoices/{id}/finalize
  • Invoice status at time of event: open

Example: Listening for both events

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const event = JSON.parse(req.body);

  switch (event.type) {
    case 'invoice.created':
      // A new draft invoice has been created.
      console.log('Draft invoice created:', event.data.object.id);
      break;

    case 'invoice.finalized':
      // The invoice is now open and payable — trigger collection logic here.
      console.log('Invoice finalized and payable:', event.data.object.id);
      break;

    default:
      console.log('Unhandled event type:', event.type);
  }

  res.sendStatus(200);
});

Pinned spec

This fix brings the implementation into full compliance with the Calmony Pay REST API Contract specification, which defines invoice.finalized as the authoritative event for invoice finalization.