Using your own backend
If you handle subscription logic and receipt validation on your own backend, follow this guide to keep Superwall in sync.
Superwall works out of the box without any backend integration. You only need this guide if your own server is involved in subscription logic.
Common reasons to integrate your own backend include:
- You validate receipts server-side (via the App Store Server API or the Google Play Developer API) instead of trusting the device.
- Your app's entitlements are served from your own API rather than derived from the local receipt.
- You share subscription state across platforms (for example, a subscription purchased on the web that unlocks features in your app).
You can integrate your own backend with Superwall in one of two ways:
- Letting Superwall handle purchases (recommended): Keep Superwall's default purchasing behavior and forward completed transactions to your backend for validation and record-keeping.
- Using a purchase controller: Take end-to-end control of the purchasing pipeline: you make the purchase, validate it on your server, and tell Superwall the user's subscription status.
PurchaseController if your backend must be the source of truth for the subscription status Superwall uses on-device. If you just need your server to know about purchases, the first option is much simpler.Letting Superwall handle purchases
By default, Superwall handles purchasing, restoring, and subscription status for you — there's nothing to configure. To keep your backend informed, listen for completed transactions and send them to your server for validation.
1. Forward transactions to your backend
Use the Superwall delegate to respond to completed transactions and send their identifiers to your backend. Your backend can then verify the purchase directly with Apple or Google:
class _MyAppState extends State<MyApp> implements SuperwallDelegate {
@override
Future<void> handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
switch (eventInfo.event.type) {
case PlacementType.transactionComplete:
final product = eventInfo.params?['product'];
// Send the transaction details to your backend for validation.
await sendTransactionToBackend(eventInfo.params);
break;
default:
break;
}
}
}2. Keep your backend in sync with webhooks
Renewals, cancellations, refunds, and billing issues happen off-device, so listening in the app isn't enough. Superwall can send webhooks to your backend for subscription and payment events — use them to keep your server's subscription records up to date without polling store APIs yourself.
When Superwall handles purchases, don't set subscriptionStatus yourself — the SDK updates it automatically after purchases, restores, and receipt checks. If your backend needs to control the status Superwall uses on-device, use a purchase controller instead.
Using a purchase controller
With a PurchaseController, you take over purchasing and restoring, and you become responsible for setting Superwall.shared.subscriptionStatus from your backend's entitlement state. See Purchases and Subscription Status for a full explanation of this mode.
1. Create a PurchaseController
Create a new file called MyBackendPurchaseController, then copy and paste the following. Replace fetchSubscriptionStatusFromBackend() and sendTransactionToBackend(...) with calls to your own API — they're placeholders for whatever your backend exposes:
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
class MyBackendPurchaseController extends PurchaseController {
// MARK: Sync Subscription Status
/// Fetches the user's entitlements from your backend and makes sure
/// Superwall knows about them.
Future<void> syncSubscriptionStatus() async {
// Ask your backend which entitlements this user has, e.g. ["pro"].
final entitlementIds = await fetchSubscriptionStatusFromBackend();
if (entitlementIds.isNotEmpty) {
final entitlements =
entitlementIds.map((id) => Entitlement(id: id)).toSet();
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements));
} else {
await Superwall.shared
.setSubscriptionStatus(SubscriptionStatusInactive());
}
}
// MARK: Handle Purchases
/// Makes a purchase from the App Store and returns its result. This gets
/// called when someone tries to purchase a product on one of your paywalls
/// from iOS.
@override
Future<PurchaseResult> purchaseFromAppStore(String productId) async {
// TODO
// ----
// Purchase `productId` with your StoreKit billing implementation
// (e.g. the in_app_purchase package), then send the transaction to
// your backend for validation.
//
// await sendTransactionToBackend(...);
await syncSubscriptionStatus();
return PurchaseResult.purchased;
}
/// Makes a purchase from Google Play and returns its result. This gets
/// called when someone tries to purchase a product on one of your paywalls
/// from Android.
@override
Future<PurchaseResult> purchaseFromGooglePlay(
String productId, String? basePlanId, String? offerId) async {
// TODO
// ----
// Launch the Google Play billing flow for `productId`, using
// `basePlanId` and `offerId` to select the right subscription offer.
// Then send the purchase token to your backend for validation and
// acknowledgement.
//
// await sendTransactionToBackend(...);
await syncSubscriptionStatus();
return PurchaseResult.purchased;
}
// MARK: Handle Restores
/// Restores purchases and returns the result. This gets called when someone
/// tries to restore purchases on one of your paywalls.
@override
Future<RestorationResult> restorePurchases() async {
// TODO
// ----
// Restore purchases with your billing implementation and send any
// transactions your backend doesn't know about to your backend.
await syncSubscriptionStatus();
return RestorationResult.restored;
}
}As discussed in Purchases and Subscription Status, this PurchaseController is responsible for handling the subscription-related logic. Take a few moments to look through the code to understand how it does this.
2. Configure Superwall
Initialize an instance of MyBackendPurchaseController and pass it in to Superwall.configure(apiKey:purchaseController:):
MyBackendPurchaseController purchaseController = MyBackendPurchaseController();
Superwall.configure(
apiKey,
purchaseController: purchaseController
);
await purchaseController.syncSubscriptionStatus();3. Sync the subscription status
Until you set the subscription status, it's .unknown — and paywall presentation is automatically delayed until it changes. Call syncSubscriptionStatus() when the app launches and whenever your backend's entitlement state changes, so Superwall always knows whether to show a paywall.
Keeping the status in sync across launches and devices
Your backend is the source of truth, so treat the on-device status as a cache of it:
- Identify users consistently. Call
identifywith the same user ID your backend uses so entitlements follow the user across devices. See User Management. - Fetch entitlements on launch. Call your backend's entitlement endpoint when the app launches (and when it returns to the foreground) and update the subscription status with the result.
subscriptionStatusis cached between app launches, so the previous value is used until your fetch completes. - Make server notifications your source of truth. Renewals, cancellations, refunds, and billing issues happen off-device. Subscribe your backend to App Store Server Notifications and Google Play's Real-time Developer Notifications so its entitlement records stay correct — the app then picks up changes the next time it syncs.
How is this guide?