Billing Milestones
A billing milestone carves out a percentage of a subscription's contract value and bills it as a one-shot invoice line when the milestone is triggered. Use them for project-style milestone billing (charge 25% on kickoff, 50% on delivery, 25% on acceptance), date-pinned releases, or external-event-driven captures.
Why this matters. Outcome-based and contract-bound billing patterns don't fit neatly into calendar cycles. Kontorion's milestones let you split a subscription's value across named events, fire a one-shot charge on each, and tie every triggered invoice line back to the milestone definition for audit. The same primitive supports a project-services firm invoicing on kickoff/delivery/acceptance, a SaaS releasing a holdback on contract close, and a marketplace charging at the moment an external system reports completion.
Anatomy
A milestone is always scoped to a subscription. The percentage carves out a slice of that subscription's contract value.
| Field | Description |
|---|---|
subscription_id | UUID. Subscription this milestone belongs to. Set automatically from the URL on create. |
product_id | UUID. The product line this milestone bills against. |
name | Human-readable label that appears on the invoice line. |
description | Optional longer description. |
percentage | Decimal share of the subscription's contract value (e.g. 25 for 25%). |
trigger_type | MANUAL, DATE, or EVENT. |
trigger_date | RFC 3339 timestamp - required for DATE triggers, ignored otherwise. |
status | pending, triggered, etc. |
triggered_at | Set when the milestone fires. |
invoice_id | The invoice the resulting line landed on. |
metadata | Free-form key-value bag. |
Trigger types
trigger_type | Fires via |
|---|---|
MANUAL | An operator calling POST /v1/subscriptions/{subscriptionId}/milestones/{id}/trigger. |
DATE | A scheduler tick after trigger_date passes. |
EVENT | An HMAC-signed external call to POST /v1/webhooks/billing-milestones/{id} (no Bearer auth - signature only). |
Creating a milestone
Code
product_id, name, and percentage are required. subscription_id is set automatically from the URL. Returns 201 Created with the milestone.
Manually triggering
Code
Generates the invoice line for this milestone and stamps triggered_at and invoice_id. Idempotent on status - re-triggering a milestone already in triggered state returns 409 INVALID_TRANSITION.
External-event triggers
For EVENT milestones, an upstream system fires the trigger via the webhook endpoint with an HMAC signature in the standard webhook auth header (see Webhooks):
Code
Rate-limited to 60 requests per minute per milestone id. The endpoint lives outside the Bearer auth chain - signature verification is the only auth.
Listing
Code
Both return the standard envelope. The list endpoint paginates via the standard cursor params.
Validation
The handler enforces:
- The total of all milestones on a subscription cannot exceed 100% (
422 MILESTONE_PERCENTAGE_EXCEEDED). - Only
pendingmilestones can trigger (409 MILESTONE_NOT_PENDING). - A milestone whose generated line already landed on a finalized invoice cannot re-trigger (
409 MILESTONE_ALREADY_INVOICED).
Audit trail
triggered_at, invoice_id, and the resulting invoice line carry full provenance. Every trigger writes audit events that link the milestone, the invoice line, and (for EVENT triggers) the request id of the inbound webhook delivery.
Next steps
- Subscriptions - the parent entity.
- Invoices - where triggered milestones land.
- Webhooks - signing scheme for the
EVENTtrigger endpoint.