Last updated: 15 September 2026 (Asia/Manila)
Research note: LegitGameplay has not hands-on tested these monetization snippets in Studio. This guide is Research-based from official Roblox Creator Hub docs (retrieved 15 September 2026). Do not treat the Luau below as hands-on verified on this site yet.
The mix-up that hurts economies is treating a consumable like a pass — or granting items from a client purchase-finished event instead of a server receipt handler.
Part of Studio Save & Earn #2 (after DataStores). If you are still setting up Studio, start with the Roblox Studio first-hour guide. For durable grants, read DataStores without wiping players. How we label work: Testing Methodology · Editorial Policy.
When to use which
| Need | Use | Why |
|---|---|---|
| VIP, permanent cosmetics, extra slots that stay | Game Pass | One-time permanent entitlement; Roblox tracks ownership |
| Coins, revives, crates, one-shot boosts | Developer Product | Repeatable consumable; player can buy many times |
| Check if they already own the perk | Pass path + UserOwnsGamePassAsync | Do not re-sell a pass as if it were a consumable |
| Grant after payment, even if the receipt retries | Product path + server receipt handler | Receipts can arrive more than once |
Official Creator Hub: Passes · Developer products.
Game Pass path (permanent entitlement)
A pass is a one-time Robux fee for a privilege that should stick: VIP lounge, permanent power-up, cosmetic that stays unlocked. Roblox records ownership. Your job on the server is to check ownership and apply privileges — not to invent a second “consumable” sale of the same perk.
Typical flow:
- Client (or server) prompts with
MarketplaceService:PromptGamePassPurchase(player, passId)after confirming they do not already own it. - On join (and after a successful prompt if you listen), server calls
MarketplaceService:UserOwnsGamePassAsync(userId, passId)and grants privileges. - Creator Hub documents
PromptGamePassPurchaseFinishedfor completed pass prompts. That is not the same as the product finished event used for developer products.
Short researched example (ServerScriptService):
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local PASS_ID = 0000000 -- replace with your pass ID
local function applyPass(player)
local ok, owns = pcall(function()
return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID)
end)
if not ok then
warn("UserOwnsGamePassAsync failed:", owns)
return
end
if owns then
-- unlock VIP / permanent perk for this session
player:SetAttribute("HasVIP", true)
end
end
Players.PlayerAdded:Connect(applyPass)Do not sell “VIP for this session only” as a Game Pass if you really mean a repeatable boost — that is a developer product.
Re-check with UserOwnsGamePassAsync on every join. Attributes like HasVIP are session chrome only — they do not persist. Ownership lives on Roblox’s pass record.
Developer Product path (repeatable consumable)
A developer product is something the user can purchase more than once: in-game currency, ammo, potions, revives, crates. Creator Hub is explicit: for one-time permanent items, use passes instead.
Sell inside the experience with PromptProductPurchase. After purchase, you must process the receipt on the server and only then grant. Return a processed / granted decision when the grant is durable; return not-processed-yet when the player is offline or the grant is not ready so Roblox can retry.
ProcessReceipt vs BindReceiptHandler (2026 docs)
Both appear in official Creator Hub documentation as of this research date (retrieved 2026-09-15).
ProcessReceipt (legacy callback — still documented)
The developer products guide still tells you to use the ProcessReceipt API to check receipts, validate User ID / Product ID / status, grant when appropriate, and acknowledge. The callback returns Enum.ProductPurchaseDecision values such as PurchaseGranted or NotProcessedYet (see MarketplaceService.ProcessReceipt).
Official warning on that same guide:
Do not use the PromptProductPurchaseFinished event to process purchases. You must use the ProcessReceipt callback instead.
BindReceiptHandler (typed 2026 API — includes DeveloperProduct)
MarketplaceService:BindReceiptHandler is official. You bind by Enum.ReceiptType. Creator Hub’s own example registers Enum.ReceiptType.DeveloperProduct (not only Robux-transfer types), keys grants on PurchaseId with DataStoreService:UpdateAsync, returns nil from the transform when not ready, and responds with Enum.ReceiptDecision.Processed or Enum.ReceiptDecision.NotProcessedYet.
When to use which (as docs stand):
- Use BindReceiptHandler with
Enum.ReceiptType.DeveloperProductwhen you want the typed receipt API shown in current engine reference samples (filterable by product ID, shared pattern with other receipt types). - ProcessReceipt remains documented in the monetization guide and engine callbacks; existing games still use it. Prefer one clear server path — do not grant from both a finished event and a receipt handler.
Do not invent APIs beyond what Creator Hub lists.
Why PromptProductPurchaseFinished is the wrong place to grant
PromptProductPurchaseFinished fires when the prompt UI finishes. It is not a durable, retry-safe receipt pipeline. Creator Hub warns you not to process purchases there for developer products. Grants belong in the server receipt handler (ProcessReceipt or BindReceiptHandler). Clients can lie; prompts can dismiss without a paid grant; receipts can retry across servers.
For passes, PromptGamePassPurchaseFinished is documented as a completion signal for the pass prompt — still pair it with UserOwnsGamePassAsync on the server rather than trusting the client alone.
Idempotent PurchaseId + UpdateAsync
Receipts can be delivered more than once. If you grant coins on every delivery, you wipe your economy. Store granted PurchaseId values in a DataStore and only grant when the transform sees a first-time ID.
Cross-link the DataStores post for the same UpdateAsync cancel rule we already documented: if the transform returns nil, the write is cancelled. pcall can still report success because the API call did not throw — treat pcall-ok + nil as cancel / not a new grant (same class of bug documented on the DataStores post).
Short researched shape (developer product via BindReceiptHandler):
local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local COINS = 100
local PRODUCT_ID = 000000 -- replace
local history = DataStoreService:GetDataStore("PurchaseHistory")
MarketplaceService:BindReceiptHandler(
Enum.ReceiptType.DeveloperProduct,
function(receiptInfo)
if receiptInfo.ProductId ~= PRODUCT_ID then
return Enum.ReceiptDecision.NotProcessedYet
end
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
return Enum.ReceiptDecision.NotProcessedYet
end
local ok, granted = pcall(function()
return history:UpdateAsync(receiptInfo.PurchaseId, function(already)
if already then
return true -- already granted; keep resolved
end
local coins = player:FindFirstChild("leaderstats")
and player.leaderstats:FindFirstChild("Coins")
if not coins then
return nil -- cancel write; retry later
end
coins.Value += COINS
return true
end)
end)
-- pcall-ok + nil means UpdateAsync cancelled — NOT a successful new grant
if not ok or granted ~= true then
return Enum.ReceiptDecision.NotProcessedYet
end
return Enum.ReceiptDecision.Processed
end,
{ PRODUCT_ID } -- optional filter
)ProcessReceipt samples on Creator Hub use the same PurchaseId + UpdateAsync idea and return Enum.ProductPurchaseDecision.PurchaseGranted / NotProcessedYet. See also Implement player data and purchasing systems.
Dummy-place checklist
- Publish a separate private dummy experience for monetization tests — not your live place with real players if you can avoid it.
- Enable Enable Studio Access to API Services under File → Experience Settings → Security only on that dummy (or other trusted test) experience. Studio then talks to API services for that experience; do not casually point live economies at Studio experiments.
- For developer products, follow Creator Hub’s ProcessReceipt / external test-mode guidance when validating receipt flow. Note: Creator Hub states items for sale in external test mode cost actual Robux — prefer low-cost test products.
- Confirm grants are idempotent: buy / simulate twice with the same
PurchaseIdpath and ensure currency does not double. - Capture Output yourself before claiming Tested/hands-on on LegitGameplay (Testing Methodology).
What not to do
- Grant from
PromptProductPurchaseFinished(or any client-only “purchase finished” signal). - Treat a Game Pass like a stackable coin pack (or a coin pack like a permanent VIP flag).
- Ignore receipt retries — grant without a
PurchaseIdkey. - Treat
pcallsuccess +UpdateAsyncnil as a successful new grant. - Enable Studio API access on production and hammer live stores while experimenting.
- Paste Amazon / affiliate / gear asides into monetization guides (blocked on this site).
FAQ
Did LegitGameplay run this in Studio?
No. This post is Research-based. We have not hands-on tested these snippets in Studio Output for this article.
Can players buy a Game Pass more than once?
Creator Hub describes passes as a one-time fee for privileges. For multiple purchases, use developer products.
Which receipt API should new code use?
Both ProcessReceipt and BindReceiptHandler are official in 2026 docs. BindReceiptHandler’s reference includes Enum.ReceiptType.DeveloperProduct with PurchaseId idempotency. ProcessReceipt remains in the developer products guide. Pick one server path and never grant from the product finished event.
Where do durable grants live?
On the server, recorded with DataStores — see DataStores without wiping players.
Sources (retrieved 2026-09-15)
- Passes — Creator Hub (retrieved 2026-09-15).
- Developer products — includes ProcessReceipt guidance and PromptProductPurchaseFinished warning (retrieved 2026-09-15).
- MarketplaceService:BindReceiptHandler — official; DeveloperProduct example (retrieved 2026-09-15).
- MarketplaceService.ProcessReceipt — callback reference (retrieved 2026-09-15).
- Implement player data and purchasing systems — atomic purchase handling (retrieved 2026-09-15).
- Experience settings (Security) — Enable Studio Access to API Services (retrieved 2026-09-15).
Next
Continue from First Hour and harden grants with DataStores without wiping players. Stuck on Output error strings? Use Contact.
