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:
- Draft creation —
POST /v1/invoicesinserts a new invoice indraftstatus and emits theinvoice.createdwebhook event. - Finalization —
POST /v1/invoices/{id}/finalizetransitions the invoice fromdrafttoopen(payable) status. This should emit theinvoice.finalizedwebhook event.
The problem
Prior to v1.0.97, the finalize endpoint incorrectly dispatched invoice.created instead of invoice.finalized. This meant:
invoice.createdfired twice for the same invoice — once on draft insertion and again on finalization.invoice.finalizednever 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:
| Scenario | Recommended action |
|---|---|
You listened to invoice.created to detect when an invoice became payable | Add a handler for invoice.finalized instead |
| You want to know when a brand-new invoice is drafted | Continue listening to invoice.created — no change needed |
You were subscribed to invoice.finalized but never received it | You 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.