Skip to content
Oclavex is in private preview, available to selected users. Public signup is blocked. Request access at [email protected].

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
  1. In Settings → API credentials, create a client and grant it provision.billing-events.write — the only scope these endpoints need.
  2. Exchange its id and secret for a token with the client_credentials grant (see Authentication), asking for that scope:
Terminal window
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.write

api.example.com stands in for your endpoint — replace it with your own host.

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.

Terminal window
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:

  • targetEntitlementId binds the subscription to an entitlement you already have — the one a customer upgraded from, say. Only an Active or Trialing subscription binds.
  • Without it, Oclavex provisions a new entitlement from the offer named in entitlementOfferKey, for customerEmail. Subscribe a webhook to Entitlement.SubscriptionAnchored to learn which entitlement that is: it names your externalReference and 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.

Terminal window
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.

Terminal window
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.

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.