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.
- A Supabase authentication UUID identifies the account; the database assigns a different numeric parent ID.1
- Children belong to that parent. The subscription row is keyed by the parent, so children share its household entitlement.2
- RevenueCat can send an anonymous ID; the backend also tries aliases before declaring a parent missing.3
- 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
How does identity reach access?
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· resolvedparent→rc· numeric IDrc→store· purchasesignin→parent· auth UUIDstore→hook· eventsub→gate· status
- Parent sign-in
The authenticated Supabase user has a UUID. Backend authentication resolves it to a parent profile.
- Parent profile
The numeric parent ID owns children and the subscription. It is distinct from the auth UUID.
- RevenueCat identity
The SDK starts anonymous. Once the session and parent ID are available, the app calls logIn with the ID as a string.
- Webhook and resolve
The backend checks the app user ID, then aliases, against existing parent profiles.
- Parent subscription
A valid event can update the parent's subscription and record a payment. The membership read derives access.
Which ID is which?
| ID | Meaning and use |
|---|---|
auth.users.id / supabase_uid | Supabase account UUID. parent_profile.supabase_uid links authentication to a parent and can be null in the schema.4 |
parent_profile.id / parent_id | Numeric database ID of the paying household owner. The app sends its string form to RevenueCat. |
kid_profiles.id | Numeric ID of an individual child. kid_profiles.parent_id points to the household parent; it is not the subscription owner. |
RevenueCat app_user_id | Customer identifier on an event. It can be the stringified parent ID or an SDK-generated $RCAnonymousID value. |
aliases | Other 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.id | Webhook delivery/event identifier used for deduplication. It is not a parent or store transaction ID. |
transaction_id | Store transaction for a purchase or renewal; recorded as gateway_payment_id, with event ID as fallback. |
original_transaction_id | Store purchase lineage used to associate a subscription across events. It can differ from a renewal transaction ID. |
Why can identity be null or anonymous?
| Situation | What the source does |
|---|---|
| Before sign-in or before the ID arrives | SDK boot is anonymous; the identity hook waits when a session exists but parent ID is unresolved.5 |
| Config request, parse failure or mock mode | Mobile 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 ID | Current 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 ID | Restore 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 unknown | Backend 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.
What do payment fields mean?
| Table and fields | Meaning |
|---|---|
user_subscriptions.id, parent_id | Subscription row key and owning parent; unique parent constraint gives at most one row per parent.10 |
plan_id, pending_plan_id, pending_plan_effective_at | Current plan reference and a future plan change with its effective time. |
status, is_trial, trial_started_at, trial_ended_at | Subscription state and trial markers. Status alone does not explain every access decision. |
current_period_start, current_period_end | Start and end of the current entitlement period. |
auto_renew, cancel_at_period_end | Renewal and cancellation scheduling flags; cancellation can leave a paid period active. |
revenuecat_original_transaction_id, revenuecat_store | Store lineage identifier and mapped store for the RevenueCat rail. |
stripe_customer_id, stripe_subscription_id, stripe_checkout_session_id, stripe_price_id | Stripe-side customer, recurring subscription, checkout and price identifiers. They are not RevenueCat IDs. |
billing_country, promo_code_redeemed, created_at, updated_at | Billing routing context, redeemed promotion and record timestamps. |
payment_transactions.id, parent_id, subscription_id | Ledger row ID, owner, and optional link to subscription. A ledger is history, not the entitlement itself. |
gateway, gateway_payment_id, gateway_order_id | Payment provider, event transaction ID, and for RevenueCat an original transaction ID when different. |
amount, currency, status, paid_at | Recorded 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_at | Refund metadata; null means not recorded, not proof that no refund exists. |
stripe_invoice_id, invoice_r2_object_key, invoice_stored_at | Stripe invoice identity and stored invoice object metadata when populated. |
subscription_plans | Catalog identifies plan, price, billing interval, credits and store product IDs. plan_id references this catalog. |
processed_webhook_events.id | Event claim for deduplication. Existence alone does not prove entitlement was granted. |
subscription_lifecycle_events | Audit 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
Where can a paid event fail to grant access?
| Stage | Possible outcome |
|---|---|
| Store or SDK | A purchase may exist on the device before RevenueCat or the backend receives it; login or sync may fail. |
| Webhook delivery | Authentication, network or processing errors can prevent application. Non-2xx retry depends on the sender. |
| Identity or entitlement | Missing parent, missing matching alias, or an entitlement mismatch is acknowledged without updating the subscription. |
| Product or existing rail | Unknown product or an active Stripe subscription can cause the RevenueCat mutation to be skipped.9 |
| Claim and apply | The 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 write | Subscription 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 UI | Backend 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. |
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.