To migrate subscriptions to Stripe, you move four things in order: customers and their saved payment methods (through Stripe's data import, which works with your previous processor), your pricing model as Stripe products and prices, the subscriptions themselves (with the Billing migration toolkit or the API), and the operational logic around them. The hard part is timing. Every migrated subscription needs a start date and billing anchor that line up with what the customer already paid, and the old system must stop charging before Stripe starts, so nobody is billed twice and nobody gets a free month.

This guide is a working plan for a Stripe Billing migration. It covers why teams migrate, what each part of the data becomes in Stripe, how card data actually moves, how to set billing anchors and trials, what Stripe supports when you move between two Stripe accounts, how to avoid double charges and gaps, and how to run the cutover, reconciliation, rollback and monitoring. It ends with a step-by-step checklist.

Why do teams migrate subscriptions to Stripe?

Billing migrations are rarely anyone's favorite project, so there is usually a specific pressure behind one. The common ones we see are:

  • Moving off an in-house billing system. A cron job, a subscriptions table and a payment gateway were enough at launch. Years later, proration, tax, dunning, invoices and plan changes are all custom code that one or two people understand.
  • Leaving a legacy billing or subscription platform. The team wants billing and payments in one place, or the current tool no longer fits the pricing model the business needs.
  • Consolidating accounts. After an acquisition, a rebrand or a change of legal entity, subscriptions live in several billing setups, sometimes several Stripe accounts, and finance wants one.
  • New pricing. Usage-based pricing, new plan tiers or annual contracts are easier to model fresh than to retrofit, and the pricing change becomes the moment to move.

The reason matters because it sets the scope. A move off an in-house system usually needs a card data import from your payment processor. A consolidation between Stripe accounts does not. A pricing change means some subscriptions must be mapped to legacy prices that new customers will never see. Write the reason and the scope down before you estimate anything.

What a Stripe Billing migration actually moves

Stripe's subscription migration overview describes three stages: set up your Billing integration, migrate customer and payment processor information, then import your subscriptions. In data terms, each record in your old system becomes a specific Stripe object, and a few records do not move at all.

What each part of the old system becomes in Stripe. Invoice history does not migrate; plan where it will live.

Before mapping, build an inventory: how many active, trialing, past-due, paused and cancelled-but-still-in-period subscriptions you have; how many plans and price points, including grandfathered ones; how many distinct discount rules; which currencies and tax setups; which payment methods; and which subscriptions have unusual terms such as custom billing dates, multiple items or contract end dates. Every unusual case found now is one you do not discover on cutover night.

Customers and payment methods: how card data moves to Stripe

Card numbers cannot be exported from your old processor into a spreadsheet and uploaded to Stripe, and you should never handle raw card data yourself. Stripe documents a dedicated payment data import process, carried out between Stripe and your previous processor:

  1. You set up your Stripe account and decide how customers will be mapped.
  2. You ask your current processor for a data export and submit a migration request to Stripe through its intake form, signed in to your Stripe account.
  3. Stripe creates a dedicated SFTP server for the migration. Your previous processor encrypts the data with Stripe's PGP key and uploads it.
  4. Stripe reviews the data, shares a validation summary, and imports it only after you approve.
  5. Stripe returns a mapping file, in CSV or JSON, linking your previous processor's customer and payment method IDs to the new Stripe IDs. You update your integration with it.

A few details from Stripe's documentation shape the plan. The import creates new Customer objects by default; if some customers already exist in Stripe, you can supply a mapping file so payment methods attach to them instead of creating duplicates. Card security codes are never imported, and they are not needed to charge a saved card. Subscriptions, invoices and other billing data are not part of this import; they move separately. And the longest wait is usually your previous processor: Stripe notes that exports can take anywhere from days to months, while the import itself typically completes within about ten working days of Stripe receiving valid data. Request the export first.

Stripe also warns about a quieter effect. Your Stripe account has its own merchant identifier, so card issuers may not recognize it at first, and you can see a temporary rise in declines. Stripe's advice is to ramp volume rather than switch everything at once, starting with customer-initiated payments before recurring ones. For a migration, that argues for moving subscriptions in batches and watching decline codes from the first batch before moving the rest.

Products, prices and coupons

Create your products and recurring prices in Stripe before importing any subscriptions, and keep a price map from every old plan identifier to a Stripe price ID. Grandfathered price points are the usual complication. Stripe's API guide suggests a placeholder product, such as a legacy plan, and inline price_data on each subscription for one-off legacy prices, so you do not clutter your catalog with hundreds of prices nobody can buy.

Discounts need their own map. The migration toolkit supports one coupon per subscription. Stripe's CSV reference also notes that if a subscription or schedule has a future billing anchor and proration_behavior set to none, updating it later unsets the coupon, so re-apply coupons after any post-import change. Time-limited discounts that are partway through their term need care: either a coupon per remaining duration, or a schedule phase that removes the discount on the right date.

Subscriptions: billing anchors, trial ends and backdating

Stripe gives you two ways to create migrated subscriptions. The Billing migration toolkit in the Dashboard takes a CSV, validates it and creates subscription schedules without code. The API route gives you full control, and Stripe recommends the Subscription Schedules API there too, because schedules can start in the future and let you review the import before anyone is billed.

Whichever route you take, four fields decide whether the customer is charged correctly:

  • start_date: when the subscription is created in Stripe. In the toolkit it must be at least 24 hours after upload (one hour in a sandbox), which gives you a review window.
  • billing_cycle_anchor: the date of the next bill. Set it to the customer's next renewal date in the old system.
  • backdate_start_date: the original start of the current period, for a subscription migrated mid-cycle. When you use it, proration_behavior must be none so Stripe does not invoice for the time between the backdated start and the migration date.
  • trial_end: for customers still in a trial, the date the trial ends in the old system, so the first charge happens when the customer expects it.
Illustrative: a paid, mid-cycle subscription moved on Feb 1. No gap, and no second charge for a period the customer already paid.

Stripe's toolkit documentation walks through the common cases. A subscription due to renew on the migration date starts on that date and is charged there, with the anchor on the next renewal. A paid subscription in the middle of its period is backdated to its original start, anchored to the upcoming renewal and created with no proration, so it waits until the next billing date. A subscription in an active cycle that was never paid in the old system can be migrated with create_prorations so Stripe invoices immediately for the remaining period and, if that fails, the subscription enters Stripe's normal dunning flow. Our guide to failed subscription payments, retries and dunning covers what happens from there.

Watch time zones. CSV dates are Unix timestamps, and a renewal date converted in the wrong zone moves a charge by a day for a large share of customers. Generate timestamps in code from the old system's stored values, never by hand in a spreadsheet, and include a sample of converted dates in the dry-run review.

Invoice history: what happens to it

Historical invoices do not migrate. The payment data import covers customers and payment methods, and the subscription tools create forward-looking subscriptions. Decide early where history will live, because customers will ask for last year's invoices and finance will need them for audits. The usual options are to keep the old system available read-only for a defined period, export invoices to PDF and store them against the customer in your own application, or both. Store the old subscription and customer IDs in Stripe metadata so support can trace any customer from Stripe back to their history.

Open balances need a decision of their own. A customer who owes money in the old system can either settle there before cutover, or be migrated with an immediate prorated invoice or a one-off invoice item in Stripe. Pick one rule per case and apply it consistently; mixing them is how customers end up paying the same debt twice.

Can you migrate subscriptions to another Stripe account?

Yes. Stripe supports moving subscriptions from one Stripe account to another, and the process has two parts. First, customer and payment method data is copied between the accounts with Stripe's self-serve copy tool, which you start from the Dashboard; no external processor is involved. Second, the subscriptions are recreated in the destination account with the same Billing migration toolkit or API used for any other source.

For the toolkit route, Stripe describes exporting subscriptions from the old account's Dashboard and mapping the export to the CSV template: the period end becomes the start date, the old subscription ID goes into metadata, and the customer, price, coupon and tax rate columns must hold IDs from the destination account. The toolkit rejects IDs that belong to the source account. Products, prices and coupons therefore have to exist in the destination account first, and your price map now maps old Stripe price IDs to new ones.

The webhook side of a Stripe-to-Stripe move is easy to forget. Your application holds customer and subscription IDs from the old account, and your webhook endpoint is registered against it. Plan the switch of API keys, endpoint registration and stored IDs as part of the cutover, and expect a period where events arrive from both accounts. Our guides to subscription webhook events and webhook idempotency and retries cover how to make handlers safe for exactly this.

How to change the payment method for a subscription after migration

Some migrated customers will need a new payment method: their card expired during the project, the import could not carry a particular method, or the first Stripe charge declined. Stripe charges a subscription using its own default payment method first, then the customer's invoice default payment method. A migrated customer without either cannot be charged automatically, which is why the toolkit blocks charge_automatically subscriptions for customers with no default payment source unless you deliberately override that check.

To change the payment method on a subscription, collect the new method with a setup flow on your own page or with the customer portal, then set it as the default on the subscription, the customer, or both. The portal's payment_method_update flow sets the new method as the customer's invoice default, so if your migrated subscriptions carry a subscription-level default, update that as well or clear it. Before cutover, run a report of customers with no default payment method and contact them through your app, not only by email.

Avoiding double charges and gaps during cutover

Stripe's guidance for third-party and in-house migrations is direct: create the new subscriptions in Stripe first, then cancel the old ones before they charge. If a customer's billing date is close to the migration, schedule the Stripe subscription to start after the old system's last charge, then cancel the old subscription. The engineering work is making that rule hold for every single customer.

  • Define the cutover window. Pick a window, often a few days, and list every renewal that falls inside it. Those customers are either charged once more in the old system and migrated after, or migrated before and switched off in the old system first. Never leave them to chance.
  • Freeze changes. During the window, block plan changes, cancellations and new sign-ups on the old system, or route them to a manual queue. A plan change made after the export is a change the migration will silently lose.
  • Make import jobs idempotent. Store a record per old subscription with its Stripe schedule ID, and send an idempotency key derived from the old ID with every create call. Stripe can prune idempotency keys once they are 24 hours old, so your own record is what protects a re-run days later.
  • Batch it. The toolkit lets you run several migrations and Stripe recommends starting with a small batch. Move a small batch, watch its first charges, then move the rest.
  • Keep a reversal path. Toolkit migrations stay scheduled for 24 hours before going live, and you can cancel a migration from the toolkit within 10 hours of scheduling it; after that, cancel schedules through the API or individually. Know which tool you will use before you need it.
An idempotent subscription import job (Node.js)
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

// One mapped row per subscription. Safe to run more than once.
export async function importSubscription(row, db) {
  const existing = await db.migration.find(row.oldSubscriptionId);
  if (existing?.stripeScheduleId) return existing.stripeScheduleId;

  const schedule = await stripe.subscriptionSchedules.create(
    {
      customer: row.stripeCustomerId, // from the import mapping file
      start_date: row.nextRenewalUnix, // old system's next renewal date
      end_behavior: "release",
      default_settings: { billing_cycle_anchor: "phase_start" },
      phases: [{ items: [{ price: row.stripePriceId, quantity: row.quantity }] }],
      metadata: { old_subscription_id: row.oldSubscriptionId },
    },
    { idempotencyKey: "migrate-sub-" + row.oldSubscriptionId + "-v1" },
  );

  await db.migration.save(row.oldSubscriptionId, { stripeScheduleId: schedule.id });
  return schedule.id;
}

This sketch takes the simplest timing strategy: each subscription is scheduled to start on the customer's next renewal date, so the old system has charged for the current period and Stripe charges the next one. It only stays correct if the old system's renewal for that date is switched off before it runs. Mid-cycle backdating, trials and legacy prices need the extra fields described above.

A step-by-step Stripe Billing migration plan

Every migration we run follows the same five phases. Each phase has an exit check, and the next phase does not start until it passes.

Migration phases, each with its own exit check. The rollback window stays open until the old system is decommissioned.
  1. Map. Inventory every subscription state and edge case. Write the field-by-field map from old records to Stripe objects, the price and coupon maps, and the rule for each timing case: due on cutover, mid-cycle paid, in trial, unpaid, cancelling at period end. Exit check: every active record has a defined destination and rule.
  2. Dry run. Import into a Stripe sandbox, using real (non-card) data shapes and test payment methods. Stripe recommends running the import at least once before live mode and notes that price IDs differ between sandbox and live, so the dry run also tests your ID remapping. Advance test clocks through the first renewal and compare every invoice amount and date with what the old system would have charged. Exit check: a diff report with zero unexplained differences.
  3. Cutover. Freeze changes, export the final snapshot, run the import in batches, verify each batch's schedules, then switch off renewals in the old system for those customers. Exit check: every migrated customer has exactly one active billing source.
  4. Reconcile. Compare counts, monthly recurring amounts and next billing dates between the old snapshot and Stripe, per plan. Watch the first charges for each batch and confirm your application's access matches billing state. Exit check: reconciliation report signed off by engineering and finance.
  5. Decommission. Put the old system into read-only mode, archive invoices and audit data, revoke its payment credentials once you are sure no charge can be made from it, and remove the dual-path code. Exit check: the old system can no longer charge anyone.

Customer communication, rollback and post-migration monitoring

Customer communication

Most customers should notice nothing except a different-looking receipt. Tell them anyway, briefly: what is changing, that their plan, price and billing date stay the same, what the new receipt or statement descriptor will look like, and how to update their payment method if asked. Send it before the first Stripe charge, not after. Customers who need to act, for example those with no usable payment method, get a separate, specific message and an in-app prompt.

Rollback plan

A rollback plan answers three questions in advance: what condition triggers it, who decides, and what exactly is undone. For a batch that has not gone live, rollback means cancelling the scheduled subscriptions and leaving the old system's renewals switched on. For a batch that has gone live, it usually means pausing further batches, fixing forward on the affected subscriptions, and refunding any incorrect charges, because the old system's renewals have already been switched off. Write both procedures and rehearse the first one during the dry run.

Post-migration monitoring

Stripe recommends monitoring migrated subscriptions from the first payment: charge dates and amounts should match the start dates you set, and decline codes such as incorrect_number point to payment methods that did not survive the import. For the first full billing cycle, watch daily:

  • Charges per day against the forecast from the old system's renewal calendar. A spike means double charging; a gap means subscriptions that never started.
  • Decline rates and decline codes for migrated customers versus new customers.
  • Open invoices that cannot be attempted because the customer has no default payment method.
  • Customers with both an active old subscription and an active Stripe subscription. This number must be zero.
  • Support tickets mentioning billing, tagged so they can be counted.

From here, this article is about Cevrynt

How Cevrynt runs Stripe Billing migrations

Cevrynt is a Stripe partner, a member of the Stripe Partner Ecosystem. Our founder, Arindam Pradhan, holds the Stripe Certified Professional, Implementation Architect certification, and the same engineers who scope a migration build and run it. We start with a feasibility check: how portable your data is, whether payment methods can move, how billing dates carry over and where the cutover risks are. We only commit to a migration plan after that.

Deliverables include the field-by-field data map, dry-run results, idempotent import jobs in your repository, and a cutover and rollback runbook. See our Stripe Billing service and the wider Stripe services hub. Stripe projects are scoped and priced in writing after a technical review; to start one, tell us about your migration.