Unlock Something for Buyers PRO
You sell a game. When someone touches the table, you want to know if they bought it. One request answers that.
You'll need: a key with customers:read.
How it works
Ask Fasty about the avatar and the product:
GET /customers/{avatar}/purchases?product_id=prd_…&limit=1The answer always comes with purchased:
{
"avatar_key": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"purchased": true,
"count": 1,
"items": [
{
"sale_id": "f4c0dc27-a5ca-100a-9c9a-f932a2f83bed",
"product": { "id": "prd_mxbjm9xtpchmet", "name": "Riot Boots" },
"status": "delivered",
"amount": 299,
"gift_from": null,
"created_at": "2026-10-25T18:36:25.548Z"
}
],
"next_cursor": null
}purchased is true when the avatar owns the product:
- Bought it or got it as a gift: yes.
- Older versions of the product count too.
- Refunded: no.
An avatar who never bought gets 200 with purchased: false, not an error.
Find the product id
Search your products by name, and read id:
curl "https://api.fastyvendor.com/api/v1/products?q=Riot" \
-H "Authorization: Bearer $FASTY_KEY"In Second Life
Put this script in your object and set it to no-copy and no-modify.
string API = "https://api.fastyvendor.com/api/v1";
string KEY = "fsk_…"; // your key, with customers:read
string PRODUCT = "prd_…"; // the product that unlocks this object
key gRequest = NULL_KEY;
key gWho;
list gAllowed; // who we already checked, so a second touch needs no request
unlock(key who)
{
llRegionSayTo(who, 0, "Unlocked! Have fun.");
// your game starts here
}
default
{
touch_start(integer n)
{
key who = llDetectedKey(0);
if (llListFindList(gAllowed, [who]) != -1) { unlock(who); return; }
if (gRequest != NULL_KEY) { llRegionSayTo(who, 0, "One moment, try again."); return; }
gWho = who;
gRequest = llHTTPRequest(API + "/customers/" + (string)who + "/purchases?limit=1&product_id=" + PRODUCT,
[HTTP_CUSTOM_HEADER, "Authorization", "Bearer " + KEY], "");
}
http_response(key id, integer status, list meta, string body)
{
if (id != gRequest) return;
gRequest = NULL_KEY;
if (status != 200)
{
llOwnerSay("Fasty: " + llJsonGetValue(body, ["error", "message"]));
llRegionSayTo(gWho, 0, "We can't check right now. Try again in a minute.");
}
else if (llJsonGetValue(body, ["purchased"]) == JSON_TRUE)
{
gAllowed = llList2List(gAllowed + [gWho], -50, -1);
unlock(gWho);
}
else
{
llRegionSayTo(gWho, 0, "You need to buy this at the store first.");
}
}
}Selling this object?
Your key would travel inside it. If copies leave your hands, keep the key on a server of yours, as below, and let the object ask your server.
On your server
The same check as a function:
async function owns(avatarKey, productId) {
const url = `https://api.fastyvendor.com/api/v1/customers/${avatarKey}/purchases?limit=1&product_id=${productId}`
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.FASTY_KEY}` } })
if (!res.ok) throw new Error((await res.json()).error.message)
return (await res.json()).purchased
}import os, requests
def owns(avatar_key, product_id):
res = requests.get(
f"https://api.fastyvendor.com/api/v1/customers/{avatar_key}/purchases",
params={"limit": 1, "product_id": product_id},
headers={"Authorization": f"Bearer {os.environ['FASTY_KEY']}"},
)
res.raise_for_status()
return res.json()["purchased"]Variations
Any color of a product. Use group_id to accept any product of a group:
GET /customers/{avatar}/purchases?limit=1&group_id=grp_…Any of several products. Separate up to 50 ids with commas:
GET /customers/{avatar}/purchases?limit=1&product_id=prd_aaa,prd_bbb,prd_cccIf Fasty can't be reached, your object decides what happens. For a game, letting known players in is usually kinder than locking everyone out.