Log in Create workspace
Skip to content

Flows

Billing flows and payment lifecycle

Choose a supported billing action in /app/billing, complete any provider step, and wait for signed webhook reconciliation before treating a plan, balance, or cancellation as final.

API docs
On this page

Start here

Follow billing from operator action through verified provider reconciliation; a browser redirect alone is not proof of payment.

Before you begin

  • Workspace owner or admin access to the billing console
  • Configured provider credentials and visibility into webhook processing

Put it into practice

  1. Select a shipped flow

    Use checkout, balance top-up, or cancel. To change an active paid plan, cancel, retain access through expiry, wait for reconciliation to Starter, then buy the desired plan.

  2. Complete provider action

    Send the operator to the provider and keep payment pending on return; wait for the callback.

  3. Wait for verified reconciliation

    Accept a plan or balance change only after the signed webhook is stored and processed. Callback processing is deduplicated.

What success looks like

Confirmed payment changes entitlement once; cancellation preserves access through provider expiry; failed callbacks remain retryable.

01 Checkout invoice

details

One-time payment

For a workspace without an active paid entitlement: 1) the operator selects a plan; 2) NOWPayments creates a checkout invoice; 3) the operator pays on the hosted page; 4) the signed webhook confirms status; 5) reconciliation upgrades the workspace. The backend refuses a new purchase for a different plan, or an invoice checkout, while an entitled subscription is active, even after cancellation but before its recorded expiry. The one exception is a balance top-up for the same plan as a live (or renewal-pending) balance subscription: that is treated as a refill and funds the existing subscription rather than opening a second one. A second purchase is also refused with payment_already_pending while an unfinished checkout exists. A hosted checkout that never received a provider payment expires automatically 24 hours after it was created (status expired), after which a new checkout can be started; a checkout that did receive a payment blocks until that payment finishes or fails.

02 Email subscription is not in the operator UI

details

/app/billing exposes three flows

The billing console only has hosted checkout (billing-checkout-form), balance top-up (billing-balance-form), and cancel. create_email_subscription/2 exists in Billing but has no LiveView event, form, or route. Do not treat email + cycle invoices + reminders as a supported operator-UI flow.

03 Balance top-up

details

Prepaid credit

1) Operator selects a paid plan and deposit currency. 2) NOWPayments creates a sub-partner balance payment. 3) The funded provider balance supports recurring plan renewal. Recurring provider credentials must be configured.

04 Subscription cancellation

details

Access through provider expiry

Canceling stops renewal, but paid access remains until the subscription's recorded expire_date; there is no additional fixed 30-day grace period. Reconciliation returns the workspace to Starter after entitlement expires. Only then can the operator purchase a different paid plan; immediate switching is not implemented.

05 Payment confirmation

details

Webhook-verified

POST /webhooks/nowpayments verifies the HMAC-SHA512 signature, stores a deduplicated inbox event, enqueues durable processing, and returns 202. Invalid signatures return 401; persistence or enqueue failures return 503. Background processing retries up to 20 attempts and plan reconciliation is safe to repeat.

06 Communication credits

details

Optional metering

BILLING_ENABLED=false leaves cross-agent message delivery unbilled. When enabled and the balance is insufficient, message dispatch persists a failed record (error code 7001) and returns HTTP 400 with detail insufficient_funds and no payment intent. The same message_id is already claimed and cannot be routed by replay. After credits are topped up, inspect the failed record and use a new message_id for a deliberate new send. Credit-purchase NOWPayments events enter the same signature-verified, idempotent webhook inbox as plan billing and use robotscenter_<workspace_id>_credits_<timestamp> order IDs.

Troubleshooting

Payment finished but the plan did not change
Check signature verification, inbox persistence, the billing queue, and reconciliation. Never grant entitlement from the browser return.
A paid plan switch is refused
Immediate switching is unsupported. Cancel, keep access through expire_date, wait for reconciliation to Starter, then buy the desired plan. Topping up the balance for the plan you already hold is not a switch and is accepted as a refill.
Message delivery returns 400 insufficient_funds
The message is saved as failed (error code 7001) before the 400. After topping up credits, inspect it; if undelivered, send intentionally with a new message_id. The old ID returns its failed result.

Where to go next

see also