Skip to main content

What This Guide Covers

Coming from Stripe Billing? The mental model is close enough that most of your integration maps one-to-one. This guide covers three things: how Stripe objects translate to Recurso, what’s genuinely different, and how to bring your live subscribers over with the cmd/import tool — idempotently, with a dry run, and without double-billing anyone mid-cycle.

Concept Mapping

Because a Recurso Plan holds multiple currency prices, a Stripe Product with separate USD and INR Prices collapses into one Recurso Plan with two entries in its prices array.

What’s Different

  • Self-hosted — Recurso runs on your infrastructure (Go + PostgreSQL, optionally TigerBeetle). Your billing data lives in your own database.
  • No percentage fee — there’s no per-transaction billing fee taken by Recurso. You pay gateway fees (Razorpay/Stripe) and your own hosting.
  • Built-in double-entry ledger — every billing event posts to a ledger with a first-class reconciliation report.
  • India compliance is native — GST (CGST/SGST/IGST), e-invoicing via the IRP, and RBI e-mandates are first-class, not add-ons. See GST Invoicing.
  • Gateways stay yours — Recurso orchestrates billing but charges through your own Razorpay or Stripe account, so you keep your processor relationship.

Migrating Your Subscribers

The Import wizard in Settings → Import data

The Import wizard in Settings → Import data

The cmd/import tool in the main repository loads plans, customers, and subscriptions directly into the database without generating invoices or calling payment gateways. Each imported subscription keeps its original billing period, so Recurso only issues the next invoice at current_period_end — migrated customers are never double-billed mid-cycle.

Step 1: Export from Stripe

Pull your active subscriptions, their customers, and the products/prices they’re on (via the Stripe Dashboard export or the Stripe API). You’ll shape this into Recurso’s import format next. Keep each subscription’s Stripe ID — it becomes the external_id that makes the import idempotent.

Step 2: Build the import file

Recurso reads a single JSON file (or three CSVs). Amounts are in minor units (paise/cents). Match Stripe’s price to a plan code, and use each customer’s email as the join key.
data.json
The importer matches plans by code, customers by email, and subscriptions by external_id (your Stripe subscription ID). Set external_id to the Stripe ID so re-running the import skips anything already migrated.
Field reference: Valid subscription status values: active, trialing, paused, canceled, past_due, unpaid. Timestamps are RFC 3339 (e.g. 2026-06-15T00:00:00Z).
Prefer spreadsheets? Pass -plans-csv, -customers-csv, and -subscriptions-csv instead of a single JSON file — the columns match the field names above.

Step 3: Dry-run first

The dry run validates the whole file and reports exactly what would happen — writing nothing. It reports every problem in one pass, so you fix your export once instead of one error at a time.
Find your tenant UUID in the dashboard under Settings, or in the response from POST /auth/register. The target tenant must already exist.

Step 4: Import for real

The tool prints a summary — N created, N skipped (already exist), N failed — and, because it’s idempotent, a partial failure is safe to fix and re-run: what already imported is skipped.
No invoices are generated by the import. The renewal worker issues each subscription’s next invoice at its current_period_end. Line up your gateway credentials (see Payment Gateways) before that first renewal so charges succeed.

Step 5: Prove it — the Compare gate

Before pointing billing at Recurso, re-run your export through the Compare gate. It diffs the export against your live Recurso data — read-only — and reports, per record:
  • Coverage — every importable source record exists in Recurso
  • Fidelity — plan amount, currency, and interval match exactly; customer identity intact
  • Continuity — billing resumes exactly where Stripe stops: a subscription whose period end drifted more than an hour is flagged as the double-billing / billing-gap risk it is
"ready": true means zero issues — safe to cut over. The same gate runs from the dashboard’s Import wizard (“Before you cut over: run the Compare gate”).

After Migrating

1

Point webhooks at Recurso

Register your endpoint with POST /v1/webhooks and verify the X-Recurso-Signature header. See Webhooks.
2

Configure tax

Set GST (India) or TaxJar (US) so the first Recurso invoice is compliant. See GST Invoicing and US Sales Tax.
3

Reconcile

After the first renewal cycle, run GET /v1/finance/reconciliation to confirm the ledger, invoices, and gateway agree.

Next Steps

Going to Production

The full pre-launch checklist

End-to-End Flow

See the complete billing lifecycle