API ReferenceBilling

Subscription events

Cancellations, pauses, scheduled changes and retractions — everything an invoice cannot express.

Subscription events

PUT https://api.faurya.com/api/v1/billing/subscription-events/{external_id}

Requires Bearer Token authentication with your Faurya API key.

Invoices tell Faurya what you billed, and their subscription lines say which periods a subscription was in force. Everything that is not a bill arrives here: a cancellation, a pause, a resumption, a change that takes effect next month — and the withdrawal of any of those.

The {external_id} in the path is your id for the event.

Record an event

const response = await fetch(
  "https://api.faurya.com/api/v1/billing/subscription-events/evt_5501",
  {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${process.env.FAURYA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      event_type: "subscription_cancellation_scheduled",
      customer_external_id: "cus_42",
      subscription_external_id: "sub_900",
      event_date: "2026-03-18T16:30:00Z",
      effective_date: "2026-04-01T00:00:00Z",
    }),
  },
);

Two dates, always both

Every event carries two instants, and Faurya never assumes they are the same one:

  • event_date — when you decided. This is the date the cancellation was requested, the upgrade was clicked, the pause was set. It cannot be more than 5 minutes in the future.
  • effective_date — when it takes hold. A cancellation at period end has an effective date weeks after its event date. It may be as far in the future as you like.

Reports ask "what was in force at time T" using effective_date, and show you "when was this decided" using event_date. Sending the same value for both is fine when a change is immediate.

A *_scheduled event additionally insists its effective_date is not before its event_date — a schedule for the past is a keying error.

Event types

Lifecycle

event_typeWhat it asserts
subscription_startThe subscription begins. May create the subscription.
subscription_start_scheduledIt will begin on the effective date. May create the subscription.
subscription_updatedNew terms now — a price, quantity or plan change.
subscription_update_scheduledNew terms from the effective date.
subscription_cancelledService ends. This is the authoritative end.
subscription_cancellation_scheduledIt will end on the effective date. The subscription stays active until then.
subscription_pausedService paused. Still a subscriber.
subscription_resumedService resumed.

Only subscription_start and subscription_start_scheduled may bring a subscription into being. Every other type names a subscription that must already exist — through an invoice line or a start event — or it is refused with 422 SUBSCRIPTION_NOT_FOUND.

Retractions

A mistake is undone by withdrawing it, never by editing it:

event_typeWhat it withdraws
subscription_event_retractedThe one event named in retracted_event_id.
scheduled_subscription_start_retractedThe latest scheduled start that has not taken hold yet.
scheduled_subscription_update_retractedThe latest scheduled update that has not taken hold yet.
scheduled_subscription_cancellation_retractedThe latest scheduled cancellation — even one whose date has passed.

A retraction writes no state of its own; it marks the state it withdraws as retracted. That means a retraction's own external_id is not stored anywhere, and re-sending it against a different target withdraws that target too.

Request body

Always required

  • event_type (string): One of the types above.
  • customer_external_id (string): The customer the subscription belongs to. Must exist.
  • subscription_external_id (string): The subscription this is about.
  • event_date (string): When you decided. ISO 8601.
  • effective_date (string): When it takes hold. ISO 8601.

Always optional

  • event_order (integer): A tie-breaker when two events share an effective instant and you know which came first.

Terms — required on the four types that state them

subscription_start, subscription_start_scheduled, subscription_updated and subscription_update_scheduled must say what the subscription now costs:

  • plan_external_id (string, required): The plan now in force. Must exist.
  • currency (string, required): ISO 4217.
  • amount (number, required): The recurring amount per billing cycle, tax included. May be 0.
  • quantity (integer): Seats or units. Defaults to 1.
  • tax_amount (number): The portion of amount that is tax. Defaults to 0, may not exceed amount. The recurring value Faurya records is amount minus this.

On every other type these five fields are refused, not ignored — a cancellation states no price, and silently dropping a price would let you believe you had re-priced the subscription.

Retraction field

  • retracted_event_id (string): Required on subscription_event_retracted, and not allowed on anything else.

Whether an update is a plan change

You never say so. Faurya compares the plan in force just before the event takes effect with the plan the event names: same plan is an update, different plan is a plan change. That shows up in your plan-change and expansion/contraction reporting automatically.

Events are immutable

An event is a decision that was taken:

  • sending the same event id with the same body again is a replay — nothing is written, you get 200;
  • sending it with a different body is 409 EVENT_IMMUTABLE;
  • undoing it is a retraction, with a new event id.

Response

The response is the subscription as it now stands — the whole picture your event just changed, so you can confirm the effect in one round trip.

{
  "object": "subscription_event",
  "external_id": "evt_5501",
  "created": true,
  "data": {
    "external_id": "sub_900",
    "customer_external_id": "cus_42",
    "started_at": "2026-01-01T00:00:00.000Z",
    "status": "ACTIVE",
    "plan_external_id": "plan_pro_monthly",
    "recurring_amount": 49,
    "currency": "USD",
    "quantity": 1,
    "monthly_recurring_amount": 49,
    "counts_as_subscriber": true,
    "not_counted_reason": null,
    "states": []
  },
  "warnings": [],
  "request_id": "req_01JQ8Z0Y5N6W3K2M4P7R9T1V3X"
}

See Subscriptions for what every field means, including the full states history.

Response codes

  • 200 OK — the event was already stored exactly as sent (a replay).
  • 201 Created — the event was recorded.
  • 400 Bad Request — invalid fields, an unknown field, or a field not allowed for this event type.
  • 401 Unauthorized — missing or invalid API key.
  • 409 Conflict — EVENT_IMMUTABLE, SUBSCRIPTION_BELONGS_TO_ANOTHER_CUSTOMER, or a concurrent write.
  • 422 Unprocessable Entity — CUSTOMER_NOT_FOUND, SUBSCRIPTION_NOT_FOUND, PLAN_NOT_FOUND or EVENT_NOT_FOUND (the event a retraction names does not exist).
  • 429 Too Many Requests — over the rate limit.