Open the panel

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=1

The answer always comes with purchased:

json
{
  "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:

bash
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.

lsl
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:

js
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
}
python
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_ccc

If Fasty can't be reached, your object decides what happens. For a game, letting known players in is usually kinder than locking everyone out.