Crossdeck University
Watch — link your users to the customers Crossdeck found Film in production
0:00 / 0:00
Lesson 2 of 3 · Migrate

Seed existing subscribers

When you connected Stripe, Crossdeck discovered your customers — but as rail-keyed records with no user ID yet. This step is the link: you tell Crossdeck which of your users owns which discovered customer.

5 min Server

When you're done: every existing subscriber is linked to your user ID, with their plan backfilled.

1 What this is & why it matters

Crossdeck found your customers — now name them

The rail discovery from move 1 gave Crossdeck a customer record for every Stripe customer it found — but each one is keyed only by its rail identity (a cus_…), with no developerUserId attached yet. Ready to be linked, but not yet linked. This step closes that gap: you read your own user table and tell Crossdeck, in one batched call, which user ID owns which rail customer.

2 How to run the import

POST /v1/migration/users, in batches of 1000

From your backend (with your secret key), send up to 1,000 rows per request. Each row carries your developerUserId plus whatever you know — rail keys, traits, and the entitlements to backfill:

await fetch("https://api.cross-deck.com/v1/migration/users", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CROSSDECK_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    users: [
      {
        developerUserId: "user_847",        // required
        email: "[email protected]",
        stripeCustomerId: "cus_Nx…",         // the rail key Crossdeck discovered
        entitlements: { pro: true },         // backfill current plan
      },
      // …up to 1000 rows per request…
    ],
  }),
});
3 How it works, piece by piece

The rail-owned gate keeps you from double-granting

Each row's developerUserId is matched to the rail-keyed customer Crossdeck discovered — via stripeCustomerId (or an Apple / Google key) — fusing the two into one canonical customer with both identities. That's the link.

The clever part is what happens to the entitlements you assert. They pass a rail-owned gate, so you can safely send pro: true for everyone without fear of conflicting with the truth:

  • If the row's Stripe customer holds a live subscription, the rail already owns that entitlement — the manual grant is skipped (no double-grant).
  • If the subscription is lapsed, it's also skipped — the rail says they're not entitled, and that wins.
  • Only customers with no rail behind them receive the asserted manual grant (a comp account, a legacy lifetime deal).

The endpoint requires a secret key and rejects publishable ones with a clear message — this is a privileged, server-only operation.

4 The green result in your dashboard

Rail customers, now linked to your users

After the import runs, the customers Crossdeck discovered now carry your user IDs, and their plan is backfilled — live subscriptions deferring to the rail, manual grants only where there's no rail to defer to. Your existing book is now Crossdeck's, correctly.

app.cross-deck.com · people
linked · plan backfilled

cus_Nx… is now user_847, Pro — granted by the live Stripe subscription, not double-granted by the import.

POST /v1/migration/users from your backend (secret key), ≤1000 rows: each row's developerUserId + rail keys links it to a discovered customer; asserted entitlements pass a rail-owned gate (live sub → skip, lapsed → skip, no-rail → grant) so you can't double-grant. Next, verify before you cut over.