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
providerand noexternalId), the moved subscription replaces it, asupsertSubscriptionwould: the Free row ends now, its usage history is kept, and it is listed inendedSubscriptions. If the paid subscription ends later, downgrade to Free as usual withupsertSubscription({ 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.
fromUserIdmay name a user you erased withdeletePerson(id, { keepMetering: true }): while that user's paid period runs, the subscription and its used quota can still be found.
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): neitherexternalIdnorfromUserId.invalid_key(401): a public key, or a revoked key.