How to monetize an app with virtual currencies

TL;DR:
- A virtual currency turns a purchase into a balance your app spends. Grant, spend, and track.
- App Store and Google Play purchases grant credits automatically. Anything else, including web payments, rewards, and promos, you grant through the API.
- Spending runs from your server today, with SDK methods in the works.
- Purchased credits and subscription-granted credits are stored separately, which is why only the second kind can expire.
Credits fit when something in your app costs you money every time someone uses it. A generation, a render, an export, an episode. If your costs stay flat no matter how much people use the product, a subscription tier is the simpler answer, and you can stop here.
For everyone else, setting up a currency in the dashboard takes an afternoon. What takes longer is everything after: getting Apple to approve a consumable before your credits UI exists, deciding what happens to a balance when someone reinstalls, and finding out which credits you're allowed to expire.
One thing to know before you start. Credits are granted automatically when someone buys a linked product on the App Store or Google Play. Sell through Stripe or Paddle and no grant fires on its own, so your backend calls the grant API after your own webhook confirms the payment.
If you haven't created a currency yet, the quickstart covers the dashboard side. This covers the rest.
What is a virtual currency in an app?
A virtual currency is a balance your app grants and spends on the user's behalf. Someone buys 500 coins for $4.99, then spends 50 to unlock an episode, 10 to generate an image, and 200 to buy a course. Credits also arrive without a purchase behind them: a referral bonus, a daily free allowance, a goodwill gesture after something went wrong. Your app moves the number either way.

Names change by category. AI apps say credits or tokens, drama apps say coins, games say gems or gold, and language apps say hearts. Same mechanic underneath, sold in both stores as consumable in-app purchases.
Three parts make it work.
Grant adds to the balance, either automatically when someone buys a linked product or renews a subscription, or through an API call when you want to hand out credits for any other reason.
Spend deducts against an action in your app.
Track records every change so you can answer the support ticket that says credits went missing.
Store purchases and renewals grant on their own. Everything on the spend side is code you write.
Do you need a backend?
For spending, today, yes.
Your app ships with a public API key, and pulling it out of the binary takes a few minutes. User IDs are just as easy to find. If the client could spend credits with only those two values, someone could drain any user's balance from outside your app entirely, and no amount of app-side code would stop them.
The secret key never leaves your server, so that's where spending happens. SDK methods for reading and spending a balance are in the works, and the security model is why they're taking the time they are.

ℹ️Send the spend request before you run the expensive work, not after. The deduction is then your check, and the only thing left for you is refunding the credit when a generation fails. More on that refund below, because it has a wrinkle.
What makes a credit balance trustworthy?
A credit balance is money you've collected and haven't earned yet, which puts it closer to a bank account than to a score in a game. Bank accounts have properties that a number in a database doesn't have, and this is where most homegrown implementations come apart.
Start with history. Every grant, spend, expiry, and refund lands on the user's profile with the reason attached, so when someone writes in claiming their coins disappeared you can go and look. Homegrown balances usually store the current number and nothing else, and that support ticket has no answer.
Then there's what happens when two spends arrive at once. A spend lock that user's balance while it runs, so the second request waits its turn, finds the balance short, and gets an error instead of pushing the balance negative. Retries are covered separately: send an idempotency key, and a repeated request returns the original result rather than charging again.
The part that surprises people is that credits remember where they came from. The ones a user bought sit apart from the ones a subscription granted, and that separation is what lets you expire the allowance without touching the packs. Expiring credits are also spent first, so a user burns through this month's allowance before touching the pack they paid for.
All of it sits on Adapty's servers with each transaction encrypted, SOC2 Type II certified, and GDPR compliant, which is the answer when your security review asks whose holding the balance.
What happens when a generation fails after you've charged for it?
You give the credit back through the API, and the credit you give back is permanent.
Credits granted through the API carry no expiry rule, since expiry is configured on the link between a currency and a product, and an API grant has no product behind it. So every refunded generation converts a piece of a user's expiring allowance into a balance that never resets. An app with a 5% failure rate quietly accumulates permanent credits on its heaviest users, which is the opposite of what the allowance is for.
Two ways to live with it. Track refunded credits on your side and account for them when you size the next allowance. Or hold the charge until the generation returns, which means keeping the spend request until you have a result and accepting the concurrency risk in the gap. A first-class hold-and-confirm operation is on the roadmap and will make this cleaner.
Either way, log the refund with the reason. When a user asks why their balance moved, you want an answer.
How do you test the purchase flow before Apple approves your consumable?
Use a StoreKit configuration file in Xcode.
The trap catches a lot of teams. Your product list comes back empty until Apple approves the consumable. Apple won't approve the consumable until your app demonstrates the credits implementation they're reviewing. So you build the entire economy without ever seeing a purchase response, and if a client wants proof that the flow works before you build it, you have nothing to show.
A StoreKit configuration file breaks the loop. Define products locally in Xcode, and StoreKit serves them to your app without App Store Connect involvement. The purchase dialog appears, your handler runs, and you can demo the full flow against products that don't exist yet. Sandbox accounts test the real store path afterward, once the product is approved.
Build against a local configuration file, submit once the credits UI exists, and verify in the sandbox. Same sequence on iOS, React Native, and Flutter.
Can credits expire?
Subscription allowances, yes. Purchased credits, no.
Apple's guideline 3.1.1 says credits or in-game currencies bought through in-app purchase may not expire, and that you should have a restore mechanism for restorable purchases. That covers coin packs and credit bundles. Someone paid cash for those, and they stay.
A subscription allowance is a different thing. Guideline 3.1.2(a) permits subscriptions to include consumable credits, gems, and in-game currencies, and the monthly grant is a benefit of the current billing period rather than a purchase of credits. Because the two pools are tracked separately, expiry applies only to the granted one.
Expiring the allowance is worth doing. A subscriber who banks credits month over month eventually holds enough to cancel and keep using the product, and your churn report will blame something else. Reset the allowance at renewal, and the unused balance stops being an exit, while heavy users buy packs instead of drawing down a hoard. Spending expiring credits first supports this: a user with both pools works through the allowance before touching what they bought, so the allowance stays the thing they're paying to renew.
Reviewers read product descriptions literally. One rejection came back saying the credits provided by the developer's IAP product had an expiration time; their currency didn't expire, a 30-day access window bundled into the consumable did. Describe packs as permanent and allowances as part of the subscription period, and don't blur them in one product.
ℹ️Two related rules: Apple requires currency bought through IAP to be consumed inside the app rather than spent on real-world goods, and Google restricts in-app virtual currencies to the app they were purchased in.
What happens to a balance when someone logs out or reinstalls?
A balance belongs to a profile, so this comes down to whether you have a durable ID for the user when you grant credits.
Anonymous users are where it breaks. Someone buys 500 coins, reinstalls, and the balance is gone because the new install produced a new anonymous profile. Adapty's docs say this outright: an anonymous user keeps their balance on the device where they earned it.
Restore doesn't rescue it either. Restore replay purchases; it doesn't rebuild a balance the user already spent down, and it shouldn't.
Identify before you grant. If a user can buy credits before you have a durable ID for them, the balance is already at risk, and every edge case after that gets harder to reason about.
What about purchases made before you added credits?
Nothing is granted retroactively. Credits start accruing from the moment the currency is linked to a product, so a user who bought a pack last month has a balance of zero when you launch.
Plan for it, because this is the ticket you'll get most in week one. Loop your existing customers and grant their balances through the API before launch, or communicate the start date in-app and let subscribers pick up their first allowance at the next renewal.
Outro
The dashboard work is an afternoon. The decisions that cost you are the quiet ones: granting credits to an anonymous profile, describing a pack in a way that trips review, launching without telling existing users their balance starts at zero.
Adapty grants credits on purchase and renewal, keeps the balance per profile with a lock on every spend, separates purchased credits from granted ones, and logs every change. Book a demo or start with the quickstart.



