Use this if you run your own Roblox game and want to sell your Enviglo products in your own shop. Each product is a Roblox developer product. When a player buys one, your game sends the receipt to Enviglo, which records the purchase and gives the player the license.
Before you start
You need an API key with the Grant and revoke licenses (licenses:write) scope. New keys have it ticked if you can grant, revoke and transfer licenses yourself. See API keys.
Your game needs Allow HTTP Requests switched on. In Studio, it's under Game Settings → Security.
Connect your Roblox experience once, and Enviglo makes a developer product whenever you give a product a Robux price.
Check your store's Roblox place ID is set, under Settings → → . Enviglo works in that place's experience.
Store
Links
In Settings → Integrations, connect in the Roblox experience card, either way:
Connect with Roblox, when the card offers it. The card says when only a limited number of stores can still connect this way.
Connect with an API key. The card lists the steps: create an Open Cloud API key in Roblox's Creator Dashboard, give it developer-product with Read and Write for your experience (and game-pass too if you sell game passes), add 0.0.0.0/0 under its accepted IP addresses, then paste it into Open Cloud API key and press Connect.
Open the product, go to Pricing, fill in Price in Robux, and press Save pricing.
Enviglo creates the developer product in your experience and fills in the product's Developer product ID, and the confirmation says it was created on Roblox at your price. Change the price later and Roblox follows. Roblox can't delete a developer product, so one that's been made stays.
Add one you made
Create a developer product for your experience on Roblox, and copy its ID.
On Enviglo, open the product and go to Delivery.
Paste the ID into Developer product ID, then press Save delivery.
A developer product can belong to only one product in your store. If another product has it, or a deleted product that people still own, Enviglo says so and doesn't save.
Roblox decides what players pay. Enviglo reads the price from Roblox and shows it on your product page. To read it again, press Check Roblox in the panel at the top of Delivery.
Record purchases from ProcessReceipt
Roblox calls your game's ProcessReceipt for each developer product purchase. Your handler sends the receipt to Enviglo with POST /api/v1/licenses:
Field
What to send
Notes
robloxId
tostring(receiptInfo.PlayerId)
Required. The buyer's Roblox user ID, as a string.
robloxProductId
tostring(receiptInfo.ProductId)
The developer product, as a string. You can send productId, the product's Enviglo ID, instead.
receiptId
receiptInfo.PurchaseId
Makes the call safe to repeat: each receipt is recorded once.
robuxSpent
receiptInfo.CurrencySpent
A whole number. Shown as what was paid on the purchase.
placeId
tostring(receiptInfo.PlaceIdWherePurchased)
Shown as where it was bought.
Return PurchaseGranted only when Enviglo answers with a success, and NotProcessedYet otherwise. Roblox then offers the receipt again later. Because Enviglo records each receipt once, a retry never records a purchase twice.
Put this in a Script in ServerScriptService, and replace YOUR_API_KEY with your key:
-- EnvigloReceipts: a Script in ServerScriptService.-- It holds your API key, so keep it a server Script. Never a LocalScript, and never in ReplicatedStorage.local HttpService = game:GetService("HttpService")
local MarketplaceService = game:GetService("MarketplaceService")
local API_URL = "https://enviglo.com/api/v1"local API_KEY = "YOUR_API_KEY"-- a key with the licenses:write scope-- Sends one receipt to Enviglo. Returns true once Enviglo has it on record.localfunctionrecordPurchase(receiptInfo)local sent, response = pcall(function()return HttpService:RequestAsync({
Url = API_URL .. "/licenses",
Method = "POST",
Headers = {
["Authorization"] = "Bearer " .. API_KEY,
["Content-Type"] = "application/json",
},
Body = HttpService:JSONEncode({
robloxId = tostring(receiptInfo.PlayerId),
robloxProductId = tostring(receiptInfo.ProductId),
receiptId = receiptInfo.PurchaseId,
robuxSpent = receiptInfo.CurrencySpent,
placeId = tostring(receiptInfo.PlaceIdWherePurchased),
}),
})
end)
ifnot sent then-- No answer at all: HTTP requests are switched off, or Enviglo couldn't be reached.
warn("Enviglo: couldn't send the purchase:", response)
returnfalseendifnot response.Success then-- Enviglo said no. The body says why, as { "error": "..." }.
warn("Enviglo: HTTP " .. response.StatusCode .. ": " .. response.Body)
returnfalseendreturntrueend
MarketplaceService.ProcessReceipt = function(receiptInfo)if recordPurchase(receiptInfo) then-- Enviglo has it on record: a new license, another copy, more subscription time,-- or a receipt it had already recorded. Unlock anything in game here too.return Enum.ProductPurchaseDecision.PurchaseGranted
end-- Not recorded. Roblox offers the receipt again later, and Enviglo records-- each PurchaseId only once, so trying again is safe.return Enum.ProductPurchaseDecision.NotProcessedYet
end
Keep your key in server scripts only. See Keep keys safe for other ways to store it, including Roblox's own Secrets.
If your game sells other developer products
A game has only one ProcessReceipt. If you also sell things that aren't on Enviglo, send only Enviglo's developer products to the API, and handle the rest as you do now. Replace the ProcessReceipt in the script above with this:
-- Developer products that belong to Enviglo products: the Developer product ID-- from each product's Delivery page.local ENVIGLO_PRODUCTS = {
[1234567890] = true,
[1234567891] = true,
}
-- Your game's own code for everything else it sells.localfunctionhandleOtherPurchase(receiptInfo)-- Give the player what they bought, then:return Enum.ProductPurchaseDecision.PurchaseGranted
end
MarketplaceService.ProcessReceipt = function(receiptInfo)ifnot ENVIGLO_PRODUCTS[receiptInfo.ProductId] thenreturn handleOtherPurchase(receiptInfo)
endif recordPurchase(receiptInfo) thenreturn Enum.ProductPurchaseDecision.PurchaseGranted
endreturn Enum.ProductPurchaseDecision.NotProcessedYet
end
You can also build that list when the server starts: GET /api/v1/products returns each product's robloxProductId, with a key that has the Read products scope. The API sends IDs as strings and receiptInfo.ProductId is a number, so key the list with tonumber().
What Enviglo answers
Any success means Enviglo has the purchase on record, so your game can finish the receipt:
Status
In the answer
What happened
201
"created": true
A new license: a first purchase, or buying again after a revoke or transfer.
201
"created": true and "added": true
Another license for a Buy more than once product.
200
"renewed": true
A subscription got its days. license.expiresAt is the new end.
200
"created": false, with no "renewed"
Nothing new to give: the receipt was already recorded, or the player already owns this One purchase product. The payment is still recorded.
200 or 201
"banned": true
You've banned this player. The purchase is recorded and the license is revoked.
discordRoleId is the product's Discord role, if it has one. Enviglo gives the role itself to buyers who've linked Discord, so you only need it if your own bot does something with it. See the API reference for every field.
The Buy on Roblox button
When a product has a developer product, Buy on Roblox on its Enviglo page opens your store's place with launch data like enviglo:buy:1234567890, where the number is the developer product's ID. Read it when the player joins and prompt the purchase:
-- EnvigloLaunch: a Script in ServerScriptService.local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)-- "Buy on Roblox" sends launch data like "enviglo:buy:1234567890".local launchData = player:GetJoinData().LaunchData or""local developerProductId = tonumber(string.match(launchData, "^enviglo:buy:(%d+)$"))
ifnot developerProductId thenreturnend-- Prompt once their character has spawned.ifnot player.Character then
player.CharacterAdded:Wait()
end-- Players can edit launch data, so it only decides what to offer.-- What they get is decided by the receipt in ProcessReceipt.local ok, err = pcall(function()
MarketplaceService:PromptProductPurchase(player, developerProductId)
end)
ifnot ok then
warn("Enviglo: couldn't prompt the purchase:", err)
endend)
Launch data can be edited by the player, so treat it as a suggestion of what to offer, never as proof of anything. What they get is decided by the receipt.
The button sends buyers to your store's Roblox place ID (Settings → Store → Links), or to Place ID for this product on the product's Delivery page if you've set one. With neither, the product page shows Sold in-game instead of the button.
Troubleshooting
The request fails before it reaches Enviglo. Usually HTTP requests are off. Turn on Allow HTTP Requests under Game Settings → Security.
401 "Invalid or revoked API key." Check the key was copied in full. If it was revoked, make a new one.
403 "This key does not have the licenses:write scope." Make a key with Grant and revoke licenses.
404 "Product not found in this store. Pass productId or robloxProductId." No product in your store has this developer product. Check Developer product ID on the product's Delivery page. The script leaves the receipt waiting, so it's recorded the next time Roblox offers it after you fix the ID.
400 with a message about the body, such as "robloxId must be numeric". Check the fields against the table above: IDs as strings with tostring(), and robuxSpent as a whole number.
409 "A license for this player and product could not be recorded. Retry the purchase later." Two requests raced. Returning NotProcessedYet lets Roblox try again.
429 "Too many licenses recorded in the last minute. Wait a moment and retry." A key can record 300 purchases a minute. Returning NotProcessedYet lets Roblox try again.
Testing in Studio. Test purchases in Studio go through ProcessReceipt too, and Enviglo records them like any other. Purchase records are permanent, so test with care, and revoke the test license afterwards.
Stock. Stock counts down when a purchase gives a player something, but it never blocks a purchase, because the player has already paid. If you use stock, check it before you prompt: GET /api/v1/products returns each product's stock.