How i made subscription status sync reliable between web and app

Early on we had messy analytics because web purchases and in-app entitlements were out of sync. That made churn analysis noisy and often wrong.

We solved it by mapping web checkout IDs to the in-app subscription record and running a verification job: when a web purchase happens we push the entitlement to the app backend and reconcile it with adapty/revenuecat. We also added a webhook listener for the payment gateway to confirm payments and a retry path if mapping failed.

Lessons learned: build idempotent syncs, surface mismatches in an easy report, and test edge cases like refunds and partial refunds. Once sync was reliable our churn cohorts finally matched reality and diagnosis became possible.

What failure modes did you see when syncing web purchases into subscription services and how did you catch them?

I implemented a small reconciliation job that runs hourly and flags mismatched entitlements.

The web generator I used created consistent IDs so mapping was trivial.

It saved hours of manual checks and made our churn numbers believable again.

Make the web checkout emit a persistent purchase id and push that id to your subscription service. Then use a webhook to reconcile and surface failures.

On our web platform changes hit the app immediately and reconciliation found edge cases early so analytics stopped lying.

We had a race where the app checked entitlement before the webhook arrived.

Adding a short grace period and retry fixed most mismatches.

use one purchase id everywhere then reconcile nightly

Common failures: duplicate purchases, partial refunds, and race conditions between mobile and web verification. Fixes that worked: single source purchase id, webhook confirmations, idempotent handlers, and a reconciliation dashboard that highlights mismatches by user. Once you can see failed mappings in one place you stop chasing phantom churn.

We logged every sync attempt and added an alert when more than 0.5% failed.

That early alert found a bug with a gateway callback that only showed up for certain cards.

I saw refunds not propagating to the app. Adding refund webhooks fixed it.

Also test voided payments.

We added a manual fix flow in the dashboard for CS to resolve odd cases quickly.