Products and perks
Model what people buy, what each purchase unlocks, and how store products map to both.
A product is something a person buys, such as a monthly subscription. A perk is a
capability your app unlocks, such as premium or remove-ads. Your app and your backend check
perks, not product IDs, so you can add a yearly plan, run a discount, or sell through a new store
without changing any access check.
Where a perk comes from. A product can grant several perks, and several products can grant one.
You manage products and perks in Studio under Products, or with the CLI, the MCP server and the API. For the step-by-step in Studio, see Manage your catalog.
Perks
A perk has a name for your team and a slug that code checks, such as premium.
- Slugs are unique in the project.
- Studio asks for names and slugs of 3–32 characters, and slugs of lowercase letters, numbers, underscores and hyphens. It applies the same rule to product slugs.
- The API, the CLI and the MCP server check only that the slug is unique (the MCP server also needs it non-empty). Keep to Studio's rule anyway, so slugs look the same wherever they were made.
- Treat a slug as permanent once an app version that checks it has shipped.
A person's current access to a perk is their entitlement, which records the purchase that granted it and until when. See Subscription lifecycle for how it follows renewals, refunds and expirations.
Products
A product has a slug, such as pro_monthly, a type, and the perks it grants:
| Type | Studio label | Use it for |
|---|---|---|
| Subscription | Subscription | Recurring access, with a billing duration: weekly, monthly, quarterly, every six months or annual. |
| One-time | Non-consumable | A purchase made once per person, such as lifetime access. |
| Consumable | Consumable | A purchase made many times, such as coins or credits. |
A product's type and billing duration are fixed once it exists. Slugs of products and perks can be
changed through the API's PATCH operations and the MCP server, but renaming one breaks every
shipped app version and paywall that still uses the old slug; regenerated
types make your build fail where code still uses it. The slug is
your cross-platform identifier: the App Store, Google Play and Stripe IDs can all differ from it.
Before anyone can buy a product, give it at least one perk and map a store product from each provider you sell through.
The SDKs show the price each store resolves for the person's storefront. Don't hard-code a price or currency in your app or paywall.
Map store products
A store product is the item a payment provider sells. Mapping it to a Voidhash product decides what buying it grants. Connect the provider first; see Payment providers (Help center).
| Provider | Identifies the store product by |
|---|---|
| Apple App Store | Product ID, with optional promotional offers |
| Google Play | Product ID and Base plan ID, with optional developer offers |
| Stripe | Product ID (prod_…) and Price ID (price_…). Voidhash reads whether the price is live or test. |
| Payment source | Product key, for all environments, production only or test only. Split it only when your test system names it differently. |
- A product can sell several store products, such as a price per currency, but a store product maps to one product at a time.
- Moving a store product to another product keeps the old mapping as history. Use
voidhash provider map <store-product> <product>or the MCP toolmap_store_product, which moves it only after you confirm. - Voidhash can read the catalogs of the App Store, Google Play, Stripe and payment sources, so you
can import store products instead of typing IDs. Catalogs are cached for up to an hour. Reading
the App Store catalog needs the App Store Connect API key. From the terminal, use
voidhash provider products; see CLI commands.
What changes reach existing buyers
Catalog changes apply to everyone who already bought, not only to new purchases. Studio warns you before each of these:
- Adding a perk to a product grants it to every current subscriber and every one-time buyer of that product.
- Removing a perk from a product, or deleting a perk, takes it away from them.
- Moving a store product to another product gives its past buyers the perks of the new product.
People see the change the next time their app loads their access, usually within minutes. A product with many buyers takes longer to update.
Purchase history protects mappings:
- A mapping with purchases can't be deleted, and its store product ID can't be edited, because the store keeps reporting renewals and refunds under the original ID. Map the new store product instead.
- A product can only be deleted after its mappings are removed.
A purchase of a store product that isn't mapped yet grants nothing, but Voidhash keeps it. Map the store product within a year and the purchase is applied with its perks. Map every store product before you sell it, so people get access as soon as they pay.
Offers
An offer is a discounted way to buy a product, such as 50% off the first year. Each store's offer is listed on the product's App Store or Google Play mapping under one offer ID, and buying it never falls back to the regular price. See Offers.
Keep generated types current
Generated types are optional: every SDK takes slugs as plain strings and works without them. They
turn your perk, product, feature flag and location slugs into TypeScript types, so a renamed or
deleted slug fails your build instead of failing silently. Only the CLI generates them, and
it is built from source for now. Run voidhash types generate after each catalog change, or
voidhash types generate --watch while you work. See
CLI commands.
Next steps
- Subscription lifecycle: how purchases grant and end access.
- Offers: sell a product at a discount.
- Payment providers (Help center): connect the App Store, Google Play, Stripe or a payment source.
- Check access in your app with Swift, Kotlin or React Native, or from your backend with Node.js, PHP, Go, Rust or over HTTP.
Something wrong or missing on this page? Tell us by email or on Discord.