vevee.transferSubscription()POST /api/v1/subscriptions/transfersk_live_

A store subscription belongs to the Apple or Google account, not to your user. When the store moves it to another of your users (a restore, or a login on another account), call this method: the subscription moves with the units already used this period, so the new account does not start again at zero. Requires SDK 1.10 or later.

Signature

transferSubscription(params: {
  toUserId: string;      // the new owner
  provider: string;      // 'revenuecat', 'polar', ...
  externalId?: string;   // the provider's subscription id
  fromUserId?: string;   // the previous owner (pass this or externalId)
}): Promise<TransferSubscriptionResponseData>

Response

interface TransferSubscriptionResponseData {
  userId: string;
  transferred: Array<{
    subscriptionId: string;
    planId: string;
    provider: string | null;
    externalId: string | null;
    usageCarried: boolean;   // the used units moved too
  }>;
  alreadyOwned: string[];    // a replayed transfer: nothing changed
  endedSubscriptions: SubRef[]; // the new owner's Free row it replaced
  ignored?: 'not_found';     // nothing matched; still a success
}

RevenueCat TRANSFER webhook

RevenueCat's TRANSFER event carries transferred_from and transferred_to but no transaction id. Pass fromUserId: every active subscription of that provider on the previous owner moves.

if (event.type === 'TRANSFER') {
  await vevee.transferSubscription({
    toUserId: event.transferred_to[0],
    fromUserId: event.transferred_from[0],
    provider: 'revenuecat',
  });
}

How usage moves

  • Usage moves only with the subscription that was giving the previous owner their limits. Another subscription on the same account moves without usage.
  • When the new owner is on a Free plan you assigned (no provider and no externalId), the moved subscription replaces it, as upsertSubscription would: the Free row ends now, its usage history is kept, and it is listed in endedSubscriptions. If the paid subscription ends later, downgrade to Free as usual with upsertSubscription({ planId: 'free' }).
  • When the new owner already has another paid subscription, both are kept: the better plan gives the limits, and the moved usage is added to the counters of matching limit groups.
  • Reservations still pending on the moved quota move too.
  • The call is idempotent. A replay answers success and changes nothing, so webhook retries are safe.
  • fromUserId may name a user you erased with deletePerson(id, { keepMetering: true }): while that user's paid period runs, the subscription and its used quota can still be found.
i
A renewal can arrive first. Since SDK 1.10, upsertSubscription naming an externalId that another of your users holds performs the same transfer, so a RENEWAL that reaches the new owner before (or without) the TRANSFER still keeps the used quota.

Errors

  • invalid_request (400): neither externalId nor fromUserId.
  • invalid_key (401): a public key, or a revoked key.