How does a signed-in parent become a paid household?

Answer

A parent owns the subscription. The app must connect the store purchase to the numeric parent profile, and the backend must apply the event before its access state changes.

  1. A Supabase authentication UUID identifies the account; the database assigns a different numeric parent ID.1
  2. Children belong to that parent. The subscription row is keyed by the parent, so children share its household entitlement.2
  3. RevenueCat can send an anonymous ID; the backend also tries aliases before declaring a parent missing.3
  4. A store charge, an accepted webhook, a ledger row and visible access are separate observations.

Applies toSource review27 Oct 2026dev-navaratri mobile 26f25f13b80635f36e582afa20d20f2e7c2260acbackend 5fb3968d7601d34793a3eda07e5d6d9dbdd42147not proof of deployed versions

1

How does identity reach access?

Sign in, identify, buy, provision
Diagram structure
  • Parent sign-in signin
  • Parent profile parent
  • RevenueCat identity rc
  • Store purchase store
  • Webhook and resolve hook
  • Parent subscription sub
  • Membership read gate
  • hook → sub · resolved
  • parent → rc · numeric ID
  • rc → store · purchase
  • signin → parent · auth UUID
  • store → hook · event
  • sub → gate · status
  1. Parent sign-in

    The authenticated Supabase user has a UUID. Backend authentication resolves it to a parent profile.

  2. Parent profile

    The numeric parent ID owns children and the subscription. It is distinct from the auth UUID.

  3. RevenueCat identity

    The SDK starts anonymous. Once the session and parent ID are available, the app calls logIn with the ID as a string.

  4. Webhook and resolve

    The backend checks the app user ID, then aliases, against existing parent profiles.

  5. Parent subscription

    A valid event can update the parent's subscription and record a payment. The membership read derives access.

2

Which ID is which?

IDMeaning and use
auth.users.id / supabase_uidSupabase account UUID. parent_profile.supabase_uid links authentication to a parent and can be null in the schema.4
parent_profile.id / parent_idNumeric database ID of the paying household owner. The app sends its string form to RevenueCat.
kid_profiles.idNumeric ID of an individual child. kid_profiles.parent_id points to the household parent; it is not the subscription owner.
RevenueCat app_user_idCustomer identifier on an event. It can be the stringified parent ID or an SDK-generated $RCAnonymousID value.
aliasesOther RevenueCat customer identifiers in the event. The backend chooses the first numeric alias that matches an existing parent if the primary ID does not resolve.
event.idWebhook delivery/event identifier used for deduplication. It is not a parent or store transaction ID.
transaction_idStore transaction for a purchase or renewal; recorded as gateway_payment_id, with event ID as fallback.
original_transaction_idStore purchase lineage used to associate a subscription across events. It can differ from a renewal transaction ID.
3

Why can identity be null or anonymous?

SituationWhat the source does
Before sign-in or before the ID arrivesSDK boot is anonymous; the identity hook waits when a session exists but parent ID is unresolved.5
Config request, parse failure or mock modeMobile resolves parentId to null; the query has a five-minute stale window. Null means the app lacks an ID, not necessarily that no parent exists.6
Normal purchase with missing IDCurrent purchaseViaRevenueCat refuses to open a purchase without a parent ID. It attempts identification when an ID exists; a swallowed SDK login failure is a separate risk.7
Restore with missing IDRestore accepts an optional ID and only calls login when it is supplied. It can therefore proceed while the SDK is anonymous.8
Webhook ID is missing, non-numeric or unknownBackend checks the primary ID and then aliases against existing parent rows. If no candidate resolves, it logs and acknowledges without a subscription mutation.39

An anonymous primary ID is not proof of failure: a valid numeric alias can resolve it. Conversely, a numeric ID that does not exist in the receiving database also fails resolution.

4

What do payment fields mean?

Table and fieldsMeaning
user_subscriptions.id, parent_idSubscription row key and owning parent; unique parent constraint gives at most one row per parent.10
plan_id, pending_plan_id, pending_plan_effective_atCurrent plan reference and a future plan change with its effective time.
status, is_trial, trial_started_at, trial_ended_atSubscription state and trial markers. Status alone does not explain every access decision.
current_period_start, current_period_endStart and end of the current entitlement period.
auto_renew, cancel_at_period_endRenewal and cancellation scheduling flags; cancellation can leave a paid period active.
revenuecat_original_transaction_id, revenuecat_storeStore lineage identifier and mapped store for the RevenueCat rail.
stripe_customer_id, stripe_subscription_id, stripe_checkout_session_id, stripe_price_idStripe-side customer, recurring subscription, checkout and price identifiers. They are not RevenueCat IDs.
billing_country, promo_code_redeemed, created_at, updated_atBilling routing context, redeemed promotion and record timestamps.
payment_transactions.id, parent_id, subscription_idLedger row ID, owner, and optional link to subscription. A ledger is history, not the entitlement itself.
gateway, gateway_payment_id, gateway_order_idPayment provider, event transaction ID, and for RevenueCat an original transaction ID when different.
amount, currency, status, paid_atRecorded payment amount, currency, state and recorded paid time. RevenueCat missing amount defaults to zero and missing currency to USD in this code.11
refund_status, refund_reason, refunded_amount, refund_reference_id, refunded_atRefund metadata; null means not recorded, not proof that no refund exists.
stripe_invoice_id, invoice_r2_object_key, invoice_stored_atStripe invoice identity and stored invoice object metadata when populated.
subscription_plansCatalog identifies plan, price, billing interval, credits and store product IDs. plan_id references this catalog.
processed_webhook_events.idEvent claim for deduplication. Existence alone does not prove entitlement was granted.
subscription_lifecycle_eventsAudit of subscription changes, separate from the current subscription row and payment ledger.

This is a field guide to the payment path. The existing 04-payments-data-model.html contains the broader physical schema.12

5

Where can a paid event fail to grant access?

StagePossible outcome
Store or SDKA purchase may exist on the device before RevenueCat or the backend receives it; login or sync may fail.
Webhook deliveryAuthentication, network or processing errors can prevent application. Non-2xx retry depends on the sender.
Identity or entitlementMissing parent, missing matching alias, or an entitlement mismatch is acknowledged without updating the subscription.
Product or existing railUnknown product or an active Stripe subscription can cause the RevenueCat mutation to be skipped.9
Claim and applyThe router claims before applying. On an exception it tries to delete the claim; if release fails, a retry can be treated as duplicate.13
Ledger writeSubscription update precedes ledger insertion; ledger insertion errors are logged and swallowed. A missing ledger row does not alone establish that the subscription update failed.11
Access and UIBackend membership, caches, JWT metadata and the app's displayed state are separate checks. A successful webhook response alone does not prove the user saw access.
6

What has actually been observed?

A prior read-only production log observation reported an initial purchase with matching RevenueCat app user and parent IDs, followed by a paid ledger entry and a subscription update. Anonymous webhook messages were also observed. This page does not independently re-query those logs, identify a deployment revision, establish a causal link between those examples, or prove the app UI result.

Opening diagram…