Refreshing Entitlements

Refresh on every foreground

Refresh on every return to the foreground, not only when coming back from the deep link. It is one line of code that covers three cases for free: the user who closes the browser before the redirect, the late webhook, and asynchronous payment methods.

// iOS
NotificationCenter.default.addObserver(
    forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main
) { _ in
    Task { await EntitlementStore.shared.refresh() }  // calls YOUR backend
}
// Android
class MainActivity : AppCompatActivity() {
    override fun onResume() {
        super.onResume()
        lifecycleScope.launch { entitlementStore.refresh() }  // calls YOUR backend
    }
}

The app always asks your backend, never Inflow. Your backend is the source of truth, fed by webhooks.

The fallback: reconcile by sessionId

If the webhook has not arrived, your backend queries Inflow directly:

GET https://api.inflowpay.xyz/api/checkout/sessions/sess_KAriHKBIiMtyPFnmIjHor
X-Inflow-Api-Key: inflow_prod_your_key
{
  "object": "checkout_session",
  "id": "sess_KAriHKBIiMtyPFnmIjHor",
  "status": "completed",
  "type": "subscription",
  "paymentId": "pay_tSHQZkFiAfKJwbmsFEJwd",
  "subscriptionId": "sub_xxx",
  "metadatas": { "appUserId": "usr_123" }
}

You know the sessionId from step 1. It is your single entry point, valid for one-time payments as well as for subscriptions, with or without a free trial.

Handle the intermediate state

If status is still open just after the payment, it means the processor has not confirmed yet. Show a "validation in progress" screen and retry a few seconds later, rather than a false failure.

Reconciliation is a fallback, not a polling loop. Back off between attempts and keep webhooks as your primary signal — the API is subject to rate limits.

Next step

Run the whole chain — including the "webhook never arrived" case — against the sandbox: Testing Your Mobile Integration.


Did this page help you?