Skip to content
WebViewGold Vibe Coding to App Store
English
Get WebViewGold

Claude Code + payments

In-app purchases for a Claude Code web app: RevenueCat inside WebViewGold

Inside the iOS and Android apps, digital subscriptions go through the stores; on your website, Stripe can stay. WebViewGold starts the store purchase through RevenueCat with one URL scheme, so the work in your codebase is a clean platform branch, a webhook and an entitlement check. This guide shows how to let Claude Code build exactly that and how to prove it works before review.

From $109 one-time per platform, or let our team do it all from $698.

  • Approval guarantee – or money back within 14 days
  • No coding needed
  • One-time payment, free updates
  • 24/7 human support
  • Made in Germany since 2014 · ISO 27001

The rule, and how the pieces fit

Apple guideline 3.1.1 says digital content, features and subscriptions unlocked inside an iOS app must use in-app purchase; Google Play's Payments policy says the same for Play Billing. Physical goods and services used outside the app are exempt. The regional exceptions have moved a lot since 2025: Apple's current guidelines allow buttons and links to other payment methods in apps on the United States storefront, and Google changed its US rules after the Epic v. Google ruling. Both are worth reading in their current form before you rely on them; the simplest approach that passes everywhere is still in-app purchase inside the app and Stripe on the web. The Stripe vs in-app purchase guide walks through the decision.

The WebViewGold side is small. RevenueCat is the default integration: your button navigates to revenuecat://purchase?external_id=USER_ID&product=PRODUCT_ID, the native store sheet appears, RevenueCat records the transaction and sends a webhook to your server, and your server writes the entitlement your web app checks. restoreinapppurchases:// restores purchases and cancelinapppurchase:// opens the store's subscription settings. On iOS 15 and higher, revenuecat://launchPaywall?external_id=USER_ID&offering=OFFERING_ID shows the RevenueCat paywall you designed in their dashboard; the docs list that scheme for iOS only, so Android uses revenuecat://purchase per product. Note that using in-app purchases in an end product requires the WebViewGold Extended License (see the in-app purchase docs).

What Claude Code does and what you do

AreaClaude CodeYou
Store consolesWrites the product list and paywall copy for you to pastePaid Apps agreement, banking and tax, subscription products in App Store Connect and the Play Console
RevenueCatExplains which fields to fill, nothing moreProject with both apps, products, entitlement, offering, webhook URL and its Authorization header value
WebViewGoldCan set the RevenueCat keys in Config.swift / Config.java when askedBundle ID and package name that match RevenueCat, builds, sandbox testers
Web appPaywall branch, restore and manage links, webhook route, entitlement table, polling after purchase, a guard command against Stripe inside the appApprove the plan, set environment variables, test the purchase on devices

Setup, in the order that avoids waiting

Store agreements and product review take the longest; start them first.

  1. Products in both stores

    In App Store Connect, sign the Paid Apps agreement, then create a subscription group with products such as pro_monthly and pro_yearly. In the Play Console, create the same product IDs as subscriptions with a base plan; Play needs the app in at least one testing track. The RevenueCat setup guide covers every field.

  2. RevenueCat project

    Add the App Store app and the Play Store app, import the products, attach them to an entitlement called pro and put them in an offering. Under Integrations → Webhooks, enter https://app.example.com/api/webhooks/revenuecat and an Authorization header value you generate yourself.

  3. WebViewGold keys

    iOS: in Config.swift set kRevenueCatEnable to true and fill revenueCatAPIKey and revenueCatProjectID; leave kRevenueCatShowDefaultOfferingPaywall false unless you really want a paywall on every launch. Android: fill REVENUECAT_API_KEY and REVENUECAT_PROJECT_ID in Config.java. Use the public SDK key RevenueCat shows for each platform's app.

  4. Run the prompt in plan mode

    The plan is the valuable part here: it should list every place your app sells or mentions a price. Approve it, let the agent implement, then add the environment variables REVENUECAT_WEBHOOK_AUTH and your database credentials on the host and deploy.

  5. Build and test in the sandbox

    TestFlight on iOS, internal testing on Android, with the checks below. If you prefer to hand the native builds and store setup to someone else, our Full Service team configures and builds both apps.

My iOS WebView is finally approved. Thanks!!

Taha Farooqui · CodeCanyon

The Claude Code prompt

Replace the product IDs and route names. The last item creates a reusable slash command that you can run before every release.

Paste into Claude Code (plan mode first)
Inside our iOS and Android apps (built with WebViewGold, detected by the token WebViewGoldApp in the user agent), digital purchases must go through the App Store and Google Play. WebViewGold integrates RevenueCat: navigating to revenuecat://purchase?external_id=USER_ID&product=PRODUCT_ID starts a native purchase, restoreinapppurchases:// restores purchases and cancelinapppurchase:// opens the store subscription settings. On iOS 15 or higher, revenuecat://launchPaywall?external_id=USER_ID&offering=OFFERING_ID shows the RevenueCat paywall; the docs list it for iOS only, so Android uses revenuecat://purchase per product. Read the In-App Purchases sections in https://www.webviewgold.com/llms-full.txt and use nothing that is not documented there. Our product IDs are pro_monthly and pro_yearly, the entitlement is pro.

Plan first and wait for my approval. The plan must list every place where we sell, price or advertise something digital (Stripe checkout, pricing page, upgrade banners, emails with payment links) and how each one behaves inside the app.

Then implement:
1. src/lib/native.ts: reuse isNativeApp() and add isIOSApp() based on the user agent.
2. Paywall actions: buyPro(productId) navigates to revenuecat://purchase with the signed-in user id (URL-encoded) as external_id. Never start a purchase for a signed-out user; send them to login first. In browsers keep the existing Stripe flow unchanged.
3. Inside the app, hide every Stripe button, web price and external payment link. Do not mention cheaper web prices.
4. The paywall shows plan name, duration, price per period, that the subscription renews automatically until cancelled, and links to /terms and /privacy. On iOS prefer launchPaywall, which shows the store prices. Where we render our own paywall, read the storefront with getstorelocation:// (window.storeLocation) and take prices from a per-storefront table in src/config/prices.ts.
5. A Restore purchases button (restoreinapppurchases://) and a Manage subscription link (cancelinapppurchase://) in settings and on the paywall, only inside the app.
6. POST /api/webhooks/revenuecat: compare the Authorization header with REVENUECAT_WEBHOOK_AUTH, read event.type, event.app_user_id, event.product_id, event.expiration_at_ms, event.environment and event.store, and upsert an entitlements row (user_id, entitlement, product_id, store, expires_at, will_renew, sandbox). CANCELLATION only means auto-renew is off; access lasts until expires_at. Add the migration.
7. GET /api/me/entitlement returns whether the user has pro right now. After a purchase starts, poll it every 4 seconds for up to a minute and again on visibilitychange, then unlock the UI. The webhook is the only source of truth; never unlock from client-side state.
8. Create .claude/commands/iap-check.md: a slash command that searches components rendered inside the app for Stripe calls, checkout links and hard-coded prices, and reports anything that would conflict with Apple guideline 3.1.1.

Finish with the environment variables, the RevenueCat dashboard settings I must enter and a test plan.

The webhook route that owns your entitlements

RevenueCat sends the Authorization header value you entered in its dashboard with every event. app_user_id is the external_id your button passed, which is why it must be your stable user ID.

typescript
// app/api/webhooks/revenuecat/route.ts
import { NextResponse } from "next/server";
import { db } from "@/lib/server";

const GRANTS = new Set(["INITIAL_PURCHASE", "RENEWAL", "PRODUCT_CHANGE", "UNCANCELLATION", "NON_RENEWING_PURCHASE"]);

export async function POST(req: Request) {
  // Same value you typed into RevenueCat -> Integrations -> Webhooks -> Authorization header
  if (req.headers.get("authorization") !== process.env.REVENUECAT_WEBHOOK_AUTH) {
    return NextResponse.json({ error: "unauthorized" }, { status: 401 });
  }
  const { event } = await req.json();
  const userId: string | undefined = event?.app_user_id;
  if (!userId || userId.startsWith("$RCAnonymousID")) {
    return NextResponse.json({ ok: true, skipped: "no user id" }); // purchase without external_id
  }

  const base = {
    userId,
    entitlement: "pro",
    productId: event.product_id,
    store: event.store,                      // APP_STORE or PLAY_STORE
    sandbox: event.environment === "SANDBOX",
  };
  const expiresAt = event.expiration_at_ms ? new Date(event.expiration_at_ms) : null;

  if (GRANTS.has(event.type)) {
    await db.entitlements.upsert({ ...base, expiresAt, willRenew: true });
  } else if (event.type === "CANCELLATION" || event.type === "BILLING_ISSUE") {
    // Auto-renew is off or payment failed: keep access until the store says it expired
    await db.entitlements.upsert({ ...base, expiresAt, willRenew: false });
  } else if (event.type === "EXPIRATION") {
    await db.entitlements.upsert({ ...base, expiresAt: new Date(), willRenew: false });
  }
  // TRANSFER events carry transferred_from / transferred_to instead; handle them if you allow account switching
  return NextResponse.json({ ok: true });
}

Verification checklist (sandbox, real devices)

  • iOS: install the TestFlight build and buy. TestFlight purchases run in the sandbox and are not charged; subscriptions there renew once a day, up to six times. To test renewals and expiration faster, sign in with a Sandbox Apple Account (App Store Connect → Users and Access → Sandbox), where the renewal rate can be adjusted.
  • Android: add your Gmail account under Play Console → Setup → License testing, install from the internal testing track (not a sideloaded APK) and buy; test purchases are not charged.
  • In RevenueCat, switch the dashboard to sandbox data and find the customer by your user ID. If the purchase shows up under an anonymous ID instead, external_id was empty.
  • Your entitlements table has a row with sandbox = true, and the app unlocks pro within a few seconds without a manual reload.
  • Delete the app, reinstall, sign in, tap Restore purchases: access comes back. Tap Manage subscription: the store's subscription screen opens.
  • Open the website in a desktop browser with the same account: pro is unlocked there too, and Stripe is still offered to web visitors.
  • Run /iap-check in Claude Code: no Stripe button, checkout link or web price is reachable inside the app.

Pitfalls

  • Unstable external_id. Pass your database user ID, not an email address that can change. Require login before the paywall; it keeps RevenueCat customers and your accounts one to one.
  • Hard-coded prices. Store prices differ per country and change with taxes. Prefer the RevenueCat paywall on iOS, and keep any per-storefront table in sync with the stores.
  • Treating CANCELLATION as the end. It only switches off auto-renewal. Revoke access on EXPIRATION or when expires_at has passed.
  • Cleared sessions. With deletecache (iOS) or CLEAR_CACHE_ON_STARTUP (Android) set to true, users are signed out on every launch and your button has no user ID to send.
  • Stripe leaking into the app. A pricing page reachable from a footer link, an upgrade email that opens inside the app, a checkout success page: each one is a 3.1.1 risk. That is what the slash command is for.

Frequently asked questions

Do I have to use RevenueCat?

No. WebViewGold also has a manual API (inapppurchase:// and inappsubscription:// with success and expiry URLs, plus injected planID, transactionIdentifier and subreceipts variables). RevenueCat is the default because it gives you one entitlement and one webhook for both stores.

Can I keep Stripe for my website?

Yes. Browser visitors keep the Stripe checkout. Both sources write the same entitlements table, so a user who paid on the web is also pro in the app, as long as the app does not advertise the web purchase.

What about the US rules for external payment links?

Apple's current guidelines allow buttons and links to other payment methods for apps on the United States storefront, and Google changed its US policy after the Epic ruling. Rules differ by region and keep changing, so read the current guideline and policy text before you build on them.

Does Claude Code need my RevenueCat secrets?

No. The public SDK keys in Config.swift and Config.java are designed to ship in the app. The webhook Authorization value and database keys live in your host's environment variables; give the agent only their names.

How does the web app know a purchase succeeded?

The docs do not define a success callback for revenuecat://purchase, so treat the webhook as the source of truth: the app polls your entitlement endpoint briefly after starting a purchase and unlocks as soon as the row appears.

Three ways to get your app into the stores

Same result, different amount of work on your side. All three come with the approval guarantee and 24/7 human support.

Do it yourself

$109 per platform, one-time

The WebViewGold license: a finished Xcode or Android Studio project. Paste your URL, set name and icon, switch on features, build and submit. Free updates forever.

See plans on the main page

Cloud Builder

$349 per license, one-time add-on

No Mac, no Xcode, no Android Studio: configure in the browser, we build both apps in the cloud and hand them to App Store Connect and the Play Console.

How the Cloud Builder works Try the free demo

Full Service

$698 per platform $798

Send us your URL and icon. Our team configures, builds and delivers your app within 72 hours, white-label, with source projects. Both platforms for $1,158.

Full Service details Powered by WeSetupYourWebViewApp

Winter Sale prices valid until December 31, 2026.

Ready to ship your app?

App Store & Google Play approval guarantee – or your money back within 14 days

Rather hand it over? Full Service: we configure, build and deliver within 72 hours, from $698 per platform. Questions? Chat with us 24/7.

Refunds are granted under the conditions listed in our FAQ: a non-content rejection by Apple and/or Google, claimed within 14 days after purchase.