How do offerings and packages decide what my paywall shows?

The SDK asks RevenueDot for offerings and shows the current one. An offering holds packages such as "Monthly" and "Yearly". Each package holds one product per app, so the same offering serves your iOS and Android apps. To change prices or plans, change the current offering on the server; the app picks it up on its next getOfferings() call.

Text
Offering "default" (current)          metadata: {"headline":"Go Pro"}
├── $rc_monthly  "Monthly"   →  pro_monthly (App Store)   ·  pro:monthly (Google Play)
├── $rc_annual   "Yearly"    →  pro_annual  (App Store)   ·  pro:annual  (Google Play)
└── $rc_lifetime "Lifetime"  →  pro_lifetime (App Store)

What the SDK receives#

GET /v1/subscribers/{app_user_id}/offerings answers only the packages whose product belongs to the calling app (the app is known from the public key):

JSON
{"current_offering_id":"default","offerings":[{"description":"Standard plans","identifier":"default","metadata":null,"packages":[{"identifier":"$rc_monthly","platform_product_identifier":"pro_monthly"},{"identifier":"$rc_annual","platform_product_identifier":"pro_annual"},{"identifier":"$rc_lifetime","platform_product_identifier":"pro_lifetime"}]}]}

The SDK then loads prices and titles from the store (or from RevenueDot for Test Store products) and builds Offerings, Offering and Package objects. For Google Play products stored as subscription:basePlan, the package also carries platform_product_plan_identifier.

Build an offering#

Shell
B="$REVENUEDOT_URL/v2/projects/$PROJECT_ID"; H="Authorization: Bearer $SECRET_KEY"
# 1. The offering. The project's first offering becomes current.
curl -s -X POST "$B/offerings" -H "$H" -H "Content-Type: application/json" -d '{"lookup_key":"default","display_name":"Standard plans"}'
# 2. A package, placed first.
curl -s -X POST "$B/offerings/$OFFERING_ID/packages" -H "$H" -H "Content-Type: application/json" -d '{"lookup_key":"$rc_monthly","display_name":"Monthly","position":0}'
# 3. One product per app in the package.
curl -s -X POST "$B/packages/$PACKAGE_ID/actions/attach_products" -H "$H" -H "Content-Type: application/json" \
  -d '{"products":[{"product_id":"'$IOS_MONTHLY'","eligibility_criteria":"all"},{"product_id":"'$ANDROID_MONTHLY'","eligibility_criteria":"all"}]}'
  • Use the standard package lookup keys ($rc_weekly, $rc_monthly, $rc_two_month, $rc_three_month, $rc_six_month, $rc_annual, $rc_lifetime) so the SDK's shortcuts such as offering.monthly work. Any other key is a custom package.
  • Order follows position, lowest first, then creation time.
  • One product per app per package. Two products of the same app can share a package only when their eligibility_criteria do not overlap (google_sdk_lt_6 and google_sdk_ge_6); otherwise the API answers 409.
  • Metadata is free-form JSON on the offering (metadata). Use it for paywall copy or feature flags; the SDK exposes it as offering.metadata.

Change the paywall without an app update#

  • Make another offering current: POST .../offerings/{offering_id} with {"is_current": true}. Exactly one offering is current. An archived offering cannot be made current.
  • Show one customer a different offering: POST .../customers/{customer_id}/actions/assign_offering with {"offering_id": "ofrng..."} (v2), or POST /v1/subscribers/{app_user_id}/offerings/{offering}/override (v1). That customer's current_offering_id becomes this offering. Send null (v2) or DELETE /v1/subscribers/{app_user_id}/offerings/override to undo it.
  • Retire an offering: archive it (.../actions/archive). The current offering cannot be archived; make another one current first.

Paywalls, targeting and experiments also change what customers see without an app update:

  • Paywalls: build a paywall in the dashboard from a template, in the visual editor or with AI, and publish it to an offering. RevenueDot sends it in the offerings response and in remote config, and RevenueCatUI's PaywallView renders it. An offering has at most one paywall. See Paywalls.
  • Targeting: an audience is a set of conditions such as country, platform, app version or active entitlements. A targeting rule gives that audience its own current offering and its own offerings for placements, which the app reads with offerings.current and currentOffering(forPlacement:). See Targeting and experiments.
  • Experiments: an experiment splits customers between two to four offerings (each with its own placement offerings), keeps each customer in the same variant, and reports conversion, revenue and retention per variant with the chance that each treatment beats the control. See Experiments.

An offering assigned to one customer through the API wins over targeting rules and experiments.

Edit this page on GitHub ↗ View as Markdown Last updated