Migrating from another platform

Move your affiliates from Rewardful, Dub, FirstPromoter, PartnerStack, or Tolt — without breaking their existing links.

The hardest part of switching affiliate platforms is your existing affiliates: their links are embedded in years of videos, posts, and bios, and their referred customers keep renewing. Partli's import is built so that migration doesn't break either.

Why existing links keep working

Partli's tracking snippet reads the ?via= query parameter — the same convention Rewardful popularized and most platforms use. When you import affiliates with their original slugs preserved, a link like yoursite.com?via=jane attributes to Jane on Partli exactly as it did on the old platform. Your affiliates don't have to update a single link — the moment you swap the old snippet for partli.js, everything in the wild keeps attributing.

The one thing to get right

Map your old platform's code/token column to Link slug in the import wizard. If a slug collides with an existing one, Partli appends a short suffix and reports it — those few partners get a new link.

In the dashboard

  1. Export your affiliates from the old platform

    • Rewardful: Affiliates → Export CSV. The token column is each affiliate's ?via= code.
    • Dub Partners: Program → Partners → Export. The link key column is the slug.
    • FirstPromoter: Promoters → Export. Use ref_id as the slug and promo_code as the coupon code name.
    • PartnerStack: Partners → Export CSV. Use partner_key as the slug.
    • Tolt: Partners → Export. Use param as the slug.
  2. Run the import wizard

    Go to Partners → Import from another platform, pick your source, and upload the CSV. Columns are auto-mapped from known export headers — check the mapping, especially Email and Link slug. The preview shows how many valid rows were detected before anything is created.

  3. Choose options and import

    Partners are created pre-approved (they never re-apply). Optionally send each a “we've moved — your link still works” email with their link and new portal, or hold it for a coordinated announcement. The import is idempotent: re-running skips partners that already exist.

  4. Import customer mappings (recurring programs)

    If partners earn on renewals, import the customer→partner mappings (step 4 of the wizard) so future renewals keep crediting them. Use the Stripe customer id (cus_…) when you have it — that's what the renewal webhook matches on.

  5. Swap the tracking snippet

    Replace the old platform's script with partli.js on your site (see JavaScript snippet), keep your Stripe connection, and run a verification test. Old links now attribute on Partli.

Coupon codes carry over

Your Stripe account doesn't change in a migration — the discount codes your affiliates share already live there. Map each affiliate's coupon in the import (or later, per partner, under Coupon aliases on their detail page) and checkouts using those codes attribute even when the buyer has no click cookie.

Unpaid balances

Earnings accrued on the old platform are usually cleanest to settle there: run a final payout before cutover. If you'd rather carry balances over, enable Import unpaid balances — each becomes an immediately-payable commission (USD) that goes out on your next payout run.

Via the API / for agents

Both imports are plain JSON endpoints (session-authenticated, manager role required).

Bulk-import partners

POST /api/workspaces/<workspace>/partners/import
{
  "programId": "uuid-of-program",
  "options": { "sendWelcomeEmail": false, "importOpeningBalances": false },
  "rows": [                                  // max 500 per call — chunk larger sets
    {
      "email": "jane@example.com",           // required; dedupe key per program
      "name": "Jane",                        // optional
      "slug": "jane",                        // optional — preserves ?via=jane
      "couponId": "abc123XY",                // optional — Stripe coupon id to alias
      "couponLabel": "JANE20",               // optional — human code name
      "openingBalanceCents": 12500           // optional — used only with importOpeningBalances
    }
  ]
}
{
  "summary": { "created": 1, "skippedExisting": 0, "errors": 0, "slugsRenamed": 0, "couponsMapped": 1 },
  "results": [ { "email": "jane@example.com", "status": "created", "slug": "jane", "couponMapped": true } ]
}

Import customer→partner mappings

POST /api/workspaces/<workspace>/customers/import
{
  "rows": [                                   // max 1000 per call
    {
      "customerExternalId": "cus_ABC123",     // Stripe customer id (preferred) or email
      "customerEmail": "buyer@example.com",   // optional
      "partnerEmail": "jane@example.com"      // must match an imported partner
    }
  ]
}

Mappings are upserts — re-running updates the partner in place. Rows whose partnerEmail doesn't match a partner in the workspace return partner_not_found; import those partners first.

Gotchas

Worth knowing

  • Imported partners still need to connect Stripe from their portal before they can be paid — the welcome email points them there.
  • Slug collisions get a random suffix and are counted in slugsRenamed — tell those partners their new link.
  • Opening balances are recorded in USD; convert other currencies before import.
  • Customer mappings only affect future renewals — they don't retroactively create commissions for past invoices.