Billing events
Your billing system knows when a customer subscribes, pays, falls behind, changes plan or leaves. Oclavex turns those facts into entitlements. The Stripe connector reads them from Stripe; billing events let any other billing system — Chargebee, Recurly, Paddle, or one you built — send them. Three endpoints take the same events the connector produces, and Oclavex applies each one exactly the way it applies Stripe’s.
| Your billing system says | You post | Oclavex |
|---|---|---|
| A subscription started, changed, fell behind, paused or ended | subscription-changed |
Creates, binds, updates, holds or ends the entitlement the subscription belongs to |
| A customer’s details changed | account-changed |
Creates or updates the organization with the same reference |
| A credit purchase was paid | credit-purchase-completed |
Adds the credits to the entitlement’s balance, once |
Set up a credential
Section titled “Set up a credential”- In Settings → API credentials, create a client and grant it
provision.billing-events.write— the only scope these endpoints need. - Exchange its id and secret for a token with the
client_credentialsgrant (see Authentication), asking for that scope:
curl -X POST https://api.example.com/auth/connect/token \ -d grant_type=client_credentials \ -d client_id=$ID -d client_secret=$SECRET \ -d scope=provision.billing-events.writeapi.example.com stands in for your endpoint — replace it with your own host.
Name things with your own references
Section titled “Name things with your own references”Every event names what it is about as scheme:kind:id, in your billing system’s words:
chargebee:subscription:AzZ9TK7pXm, chargebee:customer:16BQ4E1. The scheme is your
billing system; the kind and id are what it calls the object. Oclavex finds the
subscription’s entitlement by that reference from then on, so keep it stable.
The schemes stripe, sfdc, oclavex, manual, none and operator belong to the
built-in connectors and billing modes. An event that uses one is refused with
403 license.reserved_scheme.
Post a subscription change
Section titled “Post a subscription change”curl -X POST https://api.example.com/api/v1/provision/admin/billing-events/subscription-changed \ -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \ -d '{ "externalReference": "chargebee:subscription:AzZ9TK7pXm", "accountExternalReference": "chargebee:customer:16BQ4E1", "customerEmail": "[email protected]", "customerName": "Acme Corp", "changeType": "Created", "status": "Active", "entitlementOfferKey": "pro-annual", "planName": "professional", "quantity": 1, "startDate": "2026-09-23T10:00:00Z", "currentPeriodEnd": "2026-10-23T10:00:00Z", "latestInvoicePaid": true, "sourceEventId": "ev_16BQ4E1a", "observedAt": "2026-09-23T10:00:04Z" }'The first event for a subscription finds its entitlement one of two ways:
targetEntitlementIdbinds the subscription to an entitlement you already have — the one a customer upgraded from, say. Only anActiveorTrialingsubscription binds.- Without it, Oclavex provisions a new entitlement from the
offer named in
entitlementOfferKey, forcustomerEmail. Subscribe a webhook toEntitlement.SubscriptionAnchoredto learn which entitlement that is: it names yourexternalReferenceand the entitlement’s id. The offer must be Published. While it is Draft, Disabled or Archived, the event is still accepted with 202 but nothing is created. The event is not retried: a warning naming the subscription is added to the offer’s Audit log. Publish the offer, then send the event again.
After that, each event updates the same entitlement. Active and Trialing keep it
live, PastDue and Paused put it on hold, and Canceled ends it. The policy object
chooses what a failed payment and an ended subscription do, and whether plan changes are
followed. The choices, and what they do, are the ones the Stripe connector offers under
What Stripe events do. Leave policy
out for the defaults: a failed payment pauses the entitlement, a cancellation disables
it, and the plan is applied on the first event only.
Two dates say when things happen. currentPeriodEnd is when the current billing period
ends, so the next renewal: it is the renewal date the entitlement’s billing shows.
endDate is when the subscription itself ends. Leave it out while the subscription
renews, and set it once a cancellation is scheduled. A currentPeriodEnd before
startDate is refused.
addOnCodes makes the event an add-on subscription. It grants those add-ons on
targetEntitlementId and never changes the entitlement’s plan or status.
Send each change once, or say when it happened
Section titled “Send each change once, or say when it happened”Your system may deliver a change twice, or late. Give every event sourceEventId, your
id for the change, and observedAt, when your system saw that state. Oclavex then
ignores:
- a repeat of the last change it applied to the subscription;
- a change your system saw before that one.
An event that arrives twice, a retry after a timeout, or an older change that arrives after a newer one changes nothing. Two changes stamped in the same second are both applied.
Post an account change
Section titled “Post an account change”curl -X POST https://api.example.com/api/v1/provision/admin/billing-events/account-changed \ -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \ -d '{ "externalReference": "chargebee:customer:16BQ4E1", "changeType": "Updated", "name": "Acme Corp", "email": "[email protected]", "legalName": "Acme Corporation B.V.", "address": "Keizersgracht 1", "postalCode": "1015 CC", "city": "Amsterdam", "country": "NL", "vatNumber": "NL123456789B01", "billingDetailsOnly": true }'The organization with the same reference is created, or updated. With
billingDetailsOnly, your system owns only the invoice details: an existing
organization keeps its name, contacts and licenses, the way it does for a Stripe
customer, and no organization is created for a reference that none carries yet.
Post a credit purchase
Section titled “Post a credit purchase”curl -X POST https://api.example.com/api/v1/provision/admin/billing-events/credit-purchase-completed \ -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \ -d '{ "externalReference": "chargebee:invoice:2034", "accountExternalReference": "chargebee:customer:16BQ4E1", "entitlementId": "5f0c6c1e-9a41-4b7e-8d2a-2c1b7f0e9d11", "metric": "api-calls", "credits": 5000 }'The credits land on the entitlement’s balance for metric, once per
externalReference however often you post it.
Responses
Section titled “Responses”Each endpoint answers 202 with the event’s id as soon as the event is accepted:
{ "eventId": "0f8e3c55-4f5a-4d8c-9e6d-3a4c2b1f7e90" }The change is applied moments later. Read the entitlement to see it, or subscribe a webhook to the entitlement events. An event that can’t be applied is refused before anything happens, with a typed error:
| Status | Code | When |
|---|---|---|
| 400 | license.validation_error |
A field can’t be used: a reference that isn’t scheme:kind:id, an unknown status, a negative quantity. The detail names each field. |
| 403 | license.billing_events_not_included |
Your plan doesn’t include billing events. |
| 403 | license.reserved_scheme |
A reference uses a built-in scheme. |
| 404 | license.no_such_entitlement |
targetEntitlementId or entitlementId isn’t one of your entitlements. |
| 409 | license.billed_elsewhere |
Another provider bills the entitlement through a live subscription. It can take yours once that subscription is cancelled. |