The legacy billing workflow creates a standalone Stripe draft invoice for one
workspace and one completed calendar month. Use it only for workspaces that
still store pricing in workspaces.subscriptions and don’t have an API or
Compute Stripe subscription.
Running this workflow writes an invoice and invoice items to the Stripe
account configured on the worker. It doesn’t have a dry-run mode. Confirm the
workspace, month, pricing, usage data, and worker environment before running it.
The workflow never finalizes, sends, pays, or attaches the invoice to a
subscription. It leaves the complete invoice in draft state for review.
Understand what the workflow bills
The workflow reads pricing and usage from existing Unkey data before it writes to
Stripe:
plan and support entries in workspaces.subscriptions become full fixed
charges with quantity 1.
verifications and ratelimits entries use billable ClickHouse usage for
the requested workspace, year, and month.
- Free tiers consume usage without creating invoice lines. Each paid tier
creates one line for the quantity inside that tier.
- Fixed and usage lines have no proration. Every line belongs directly to the
standalone invoice, not to a subscription.
The selected month must be complete in UTC. The workflow uses the current legacy
subscriptions JSON, not a historical pricing snapshot. Before billing an older
month, confirm that the stored prices and tiers applied during that month.
This process doesn’t replace subscription billing or Deploy billing. The workflow
stops if billing_subscriptions contains an API or Compute Stripe subscription
ID for the workspace.
Complete the preflight checks
Complete every check before you invoke the workflow. It starts writing to
Stripe after the database and usage checks pass.
- Confirm the workspace ID and customer-approved billing month.
- Confirm the month has ended in UTC.
- Confirm ClickHouse ingestion is complete for that month. A missing monthly
aggregate can appear as zero usage.
- Inspect the workspace’s
subscriptions JSON and confirm each product ID,
fixed price, free tier, paid tier, and boundary against the agreement that
applied during the selected month.
- Confirm
stripe_customer_id belongs to the intended customer in the
intended Stripe environment.
- Confirm both subscription IDs are empty. Don’t bypass this check to bill a
subscription customer.
- Search Stripe for an existing invoice for the workspace and billing month.
The workflow also performs an account-wide metadata scan and stops if it
finds multiple matches.
You can inspect the MySQL records with this read-only query:
Stop if the workspace or billing record is deleted, the legacy subscriptions
JSON is empty or unexpected, the customer ID is wrong, or either subscription
ID is present.
Create the draft invoice
Invoke Restate ingress for workspace ws_XXX and July 2026:
The worker exposes this workflow only when a real ClickHouse client and Stripe
secret are configured.
The workflow performs these operations in order:
- Validates the request and completed month.
- Loads the workspace, legacy pricing, Stripe customer, and subscription
state from MySQL.
- Loads verification and ratelimit usage from ClickHouse when those products
exist in the legacy pricing.
- Validates all prices and tiers and builds at most 250 invoice lines.
- Scans Stripe for an invoice tagged with the tool source, workspace ID, and
billing period.
- Creates a standalone invoice with
auto_advance=false and excludes unrelated
pending customer invoice items, or resumes one matching draft.
- Adds only missing invoice lines with deterministic idempotency keys.
- Retrieves and validates the invoice and every line again before reporting
success.
MySQL, ClickHouse, and Stripe operations are durable Restate steps. External
provider retries are bounded. If an invocation fails, use the recovery
procedure below instead of changing the invoice manually.
A successful invocation returns JSON in this form:
Copy the invoice ID into the operational ticket so another operator can audit
what was created.
Review the invoice in Stripe
Open the returned invoice ID in the worker’s Stripe account. Complete this
review before any separate finalization process:
- Confirm the invoice status is
draft and automatic advancement is off.
- Confirm the Stripe customer and workspace metadata match the request.
- Confirm the billing-period metadata and displayed custom field match the
requested UTC month.
- Confirm there is no subscription association.
- Compare every fixed charge with the legacy subscriptions JSON.
- Compare every usage tier quantity with the approved ClickHouse totals and
tier boundaries.
- Confirm unrelated pending customer invoice items aren’t present.
- Record the review and invoice URL in the operational ticket.
The workflow doesn’t finalize the invoice. Use the separately approved billing
process after review if the draft must be finalized or sent.
The draft uses Stripe’s charge_automatically collection method. Finalizing
it through another process can initiate collection from the customer’s
default payment method. Don’t finalize the draft until the invoice and the
collection action have both been approved.
Recover from an interrupted run
MySQL, ClickHouse, and Stripe steps retry for up to 15 minutes. If a step
exhausts that retry window, the invocation completes with a terminal failure.
After fixing the cause, invoke the workflow again with the same request. Stripe
metadata, idempotency keys, and reconciliation ensure the existing draft is
resumed and only missing lines are created.
Don’t delete lines, add manual lines, or change workflow metadata before retrying.
The workflow stops when an existing line differs from the expected product,
quantity, period, currency, or charge specification. This fail-closed behavior
prevents a retry from silently changing an invoice.
Investigate instead of bypassing these errors:
- Active subscription: The workspace is handled by subscription billing or
has inconsistent billing data. Don’t create a legacy standalone invoice.
- Multiple invoices: More than one Stripe invoice has the same workspace
and period metadata. Review all matches before taking any action.
- Unexpected invoice state: The matching invoice has a different customer,
isn’t a draft, has automatic advancement enabled, or has a subscription
parent. Don’t rerun against another Stripe account to avoid the error.
- Unexpected or changed line: Someone or another process changed the draft,
or the legacy pricing changed between runs. Compare the draft with the source
data and resolve the discrepancy manually.
- Incomplete month: Wait until the UTC month has ended and usage ingestion
is complete.
- ClickHouse or MySQL error: Restore read access or service availability.
The workflow doesn’t treat query failures as zero usage.
If a draft needs to be deleted, finalized, voided, or otherwise corrected,
handle that as a separate reviewed Stripe operation. The billing workflow performs
none of those actions.