Skip to content

Migrate from another billing system

Migrations are achievable — typically over a 2-week window — if you sequence them right. The Paylera import API accepts customer, payment-method, and subscription records with their existing IDs preserved, so foreign keys in your application keep working.

The shape of a migration

day -14 discovery: count customers, plans, payment methods, currencies
day -10 set up live tenant; recreate catalog (codes match)
day -7 dual-write payment-method tokens to both systems
day -3 dry-run import against sandbox; verify totals
day 0 cutover window: import customers + active subs; flip API base URL
day +7 reconcile invoices and ledger against source-of-truth report
day +14 shut down old system; archive its data

Resist the urge to compress. The ledger reconciliation at day +7 is where pain hides.

1. Discovery

You need a list of:

  • Active customers and their email + foreign keys.
  • Active subscriptions with plan, billing anchor, current period start / end, trial end, commitment status.
  • Payment methods on file (token + last 4 + brand + expiry).
  • Outstanding invoices in open, partially_paid, past_due.
  • In-flight refunds and chargebacks.

If any of those numbers can’t be exact, they will come back to bite you in reconciliation.

2. Recreate the catalog

The plan codes you import must exist in the live tenant before the import. Use Build a catalog — same shape as your previous system, code-matched so subscriptions land on the right plan.

For pricing changes (you’re moving systems anyway, why not) — keep the old catalog also present with the original codes, and let grandfathered customers stay on it.

3. Migrate payment method tokens

This is the only step that can’t be done after cutover. Card tokens are provider-specific (a Stripe pm_… is meaningless to Paylera unless your live tenant uses the same Stripe account).

Two paths:

  • Same provider, same account: your provider gives you a way to reference existing tokens against a new “platform” or PaymentIntent flow. Paylera’s import accepts the existing token directly:

    POST /v1/admin/import/payment-methods
    {
    "items": [
    { "customer_id": "cus_…", "type": "card", "external_token": "pm_old_…", "last4": "4242", "brand": "visa", "exp": "12/27" },
    ]
    }
  • Different provider or same provider, different account: you must re-collect cards. The standard pattern is to send “update your payment method” emails 2 weeks before cutover with a hosted-checkout setup-mode session. Customers who don’t migrate end up in dunning after cutover; that’s recoverable but noisy.

4. Import customers and subscriptions

POST /v1/admin/import/customers
{ "items": [ ] }

Each customer record can include external_ids: { stripe: "cus_old…", your_app: "u_28471" } — Paylera persists these and they’re searchable via GET /v1/customers?external_id=….

POST /v1/admin/import/subscriptions
{
"items": [
{
"customer_id": "cus_…",
"plan_code": "pro-monthly-usd",
"status": "active",
"current_period_start": "2026-04-15T00:00:00Z",
"current_period_end": "2026-05-15T00:00:00Z",
"billing_cycle_anchor": "2026-04-15T00:00:00Z",
"trial_end": null,
"default_payment_method_id": "pm_…",
"metadata": { "external_id": "sub_old_…" }
},
]
}

Imports are upserts by metadata.external_id when present — you can re-run safely. Imported subscriptions don’t trigger an immediate invoice; the next billing cycle proceeds as scheduled.

5. Cutover

The cutover is two changes:

  1. Your application starts calling Paylera (new base URL, new key) for subscription / invoice / payment operations.
  2. Your provider’s webhooks point at the Paylera ingress, not the old billing system.

Webhooks during the cutover window are tricky — both systems may receive duplicates. Configure both to be idempotent on your handler.

6. Reconciliation

A week after cutover, run the same totals against both systems and make them match:

  • Active subscription count by plan code.
  • MRR by currency.
  • Open AR balance.
  • Recognised revenue for the cutover month.

The GET /v1/reports/* endpoints make these queryable. If anything doesn’t match, the most likely causes are:

  • A subscription on the old system that was missed in the import.
  • A subscription whose current_period_end was off by a day (timezone bugs).
  • Refunds in flight at cutover that one system saw and the other didn’t.

Fix in Paylera (operator-driven adjustments are audited). Don’t try to edit the old system after cutover.

7. Shutdown

Once reconciled, shut down the old billing system’s webhook delivery and revoke its API keys. Keep its data archived for the longer of:

  • Your retention policy (typically 7 years for billing records).
  • Any open dispute / chargeback windows (180 days for cards).

Common pitfalls

  • Lost trial end dates. Preserve trial_end exactly. A customer who thinks they have 5 days left and gets billed instead is a refund + a churn signal.
  • Lost commitment terms. If the old system tracked commitments, re-import them or you’ll let customers cancel mid-commitment.
  • Mid-period plan changes. A subscription that just upgraded before cutover may have a proration credit pending on its next invoice. Carry the credit by importing it as a wallet balance.
  • Affiliate attribution. Re-import attributions explicitly via POST /v1/admin/import/affiliations — don’t trust your CRM to be authoritative.

Get help

Migrations of more than ~5,000 active subscriptions warrant a conversation. Email migrations@paylera.io and we’ll loop in someone who’s done your source system before.