Selley Developer Docs
Build integrations, automation, and custom tooling on top of your Selley store. The REST API covers products, orders, customers, coupons, themes, webhooks, and more — all JSON over HTTPS with Bearer API key authentication.
Create API keys in Dashboard → Settings → Security. Use https://selley.io/api/v1 (or your custom domain with /api/v1).
Quick start
First API call in under a minute.
API reference
Scan every endpoint, then drill into examples.
Product types
Digital, Serial, Mystery Box, Dynamic, and more.
Provably fair
How mystery box odds and verification work.
Webhooks
Real-time order and fulfillment events.
Themes API
Pull and push storefront themes via CLI.
API Root URL
Quick example
curl https://selley.io/api/v1/products \
-H "Authorization: Bearer YOUR_API_KEY"Quick start
Get from zero to your first API call in four steps. You need a Selley account and an API key with access to your shop.
- 1Create an API key in Dashboard → Settings → Security. Copy the secret once — it is shown only at creation time.
- 2Set your base URL to
https://selley.io/api/v1(or your custom domain with/api/v1). - 3Send a test request — list your live products to confirm authentication works.
- 4Subscribe to webhooks in Developers → Webhooks so your backend reacts to orders and fulfillment in real time.
curl "https://selley.io/api/v1/products?limit=5" \
-H "Authorization: Bearer sk_live_YOUR_KEY"Multi-shop accounts
X-Selley-Shop: your-shop-slug to every request so data is scoped to the correct shop.Endpoints at a glance
All routes are relative to https://selley.io/api/v1. Click a row to jump to full parameters, examples, and response shapes.
| Method | Path | Description | Group |
|---|---|---|---|
| GET | /products | List products | Products |
| GET | /products/:id | Get product | Products |
| POST | /products | Create product | Products |
| PUT | /products/:id | Update product | Products |
| DELETE | /products/:id | Delete product | Products |
| GET | /orders | List orders | Orders |
| GET | /orders/:id | Get order | Orders |
| PUT | /orders/:id | Update order | Orders |
| GET | /coupons | List coupons | Coupons |
| POST | /coupons | Create coupon | Coupons |
| GET | /blacklists | List blacklists | Blacklists |
| GET | /categories | List categories | Categories |
| GET | /groups | List groups | Groups |
| GET | /customers | List customers | Customers |
| GET | /subscriptions | List subscriptions | Subscriptions |
| GET | /themes | List themes | Themes |
| PUT | /themes/:themeId | Update theme | Themes |
Full CRUD docs for each resource appear below — including coupons, blacklists, categories, groups, and webhooks.
Authentication
Every request must include your API secret key as a Bearer token in the Authorization header. Generate and manage keys in your dashboard under Settings → Security. All requests must be made over HTTPS.
Setup Authentication Header
curl https://selley.io/api/v1/products \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx"Multiple Shops
If your account has more than one shop, pass the X-Selley-Shopheader with the shop's ID or slug on each request. Without this header the first (oldest) shop is used automatically.
curl https://selley.io/api/v1/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Selley-Shop: my-shop-slug"Product types
Every product has a type that controls checkout behavior, fulfillment, and what appears on the customer invoice. Choose the type when creating a product in the dashboard or via POST /products.
| Type | Best for | Delivery |
|---|---|---|
Service | Manual fulfillment, coaching, access you grant yourself | Invoice message + optional file links |
Digital | Downloadable files (PDF, ZIP, etc.) | Secure download links on invoice |
Physical | Shipped goods | Tracking info when you add it |
Serial | License keys, game codes, account credentials | One unique code per quantity from your stock pool |
Subscription | Recurring billing via Stripe | Ongoing access; renewals fire subscription webhooks |
InfoCard | Display-only product (no checkout) | N/A — informational storefront card |
Dynamic | Custom API-driven fulfillment | POST to your Dynamic Webhook URL per line item |
MysteryBox | Loot boxes / case openings with published odds | Provably fair roll + reveal on invoice with verification proof |
Mystery box (provably fair)
__mysteryClientSeed.Fulfillment & delivery
When an order is marked paid, Selley runs fulfillment automatically based on each line item's product type. Delivered content appears on the customer invoice at /shop/[store]/invoice/[orderNumber] and in order API responses where applicable.
Serial & Digital
Codes or files are pulled from inventory at payment time. Stock decrements per unit. Bulk copy/download on the invoice exports raw codes only (one per line, no product name prefixes).
Dynamic
Selley POSTs a rich payload to the product's Dynamic Webhook URL (INVOICE.ITEM.DELIVER-DYNAMIC). Your server responds with delivery content (text, links, or JSON) shown on the invoice.
Mystery box
Outcomes are rolled once per quantity using committed randomness. The invoice shows the revealed item, odds snapshot, commitment hash, roll index, and verification steps. Pool stock for each outcome decrements when that outcome is won.
Service & Physical
Use the delivery message configured on the product. For physical goods, add tracking in the dashboard or via order updates; customers see tracking on the invoice when present.
Idempotency
Mystery box fairness
Selley mystery boxes (case openings, loot boxes) use a provably fair system. Buyers see exact odds and a cryptographic commitment before paying. After purchase, they can verify that Selley — not the shop owner — rolled the outcome using the committed randomness.
Share this guide with customers from your product page via the How we prove it's fair link on every mystery box listing.
What buyers see before purchase
Every live mystery box product page on Selley shows:
- Drop table — Each outcome, weight, percentage chance, and remaining stock.
- Fairness commitment —
SHA-256(server seed)published before checkout. The seller cannot change the seed without changing this hash. - Pool version — Increments when odds or stock change, with a new commitment. Buyers should note version + hash at purchase time.
- Optional client seed — Buyers may set
__mysteryClientSeedso part of the randomness comes from them.
For merchants
Why the commitment hash matters
Before payment, Selley stores a random server seedand shows only its hash on the product page. SHA-256 is one-way — you cannot reverse the hash to pick a favorable seed after seeing the buyer's order.
After payment, Selley reveals the server seed on the invoice. Verification is simple: recompute the hash and confirm it matches what was shown before purchase.
Verification
shown_before_purchase === SHA-256(revealed_server_seed)Pool changes
How rolls work
Selley runs fulfillment automatically when an order is paid. For each mystery box quantity:
- Resolve client seed from
__mysteryClientSeedor generate one. - Assign a unique nonce per open (per quantity index).
- Compute
proofHash = HMAC-SHA256(serverSeed, clientSeed + ":" + nonce). - Derive roll index:
parseInt(proofHash[0..12], 16) mod totalWeightwheretotalWeightis the sum of weights for in-stock outcomes. - Walk outcomes in order; the roll selects exactly one item matching published weights.
- A second HMAC pass picks which deliverable code from that outcome's stock.
const crypto = require("crypto");
function rollHash(serverSeed, clientSeed, nonce) {
return crypto.createHmac("sha256", serverSeed)
.update(`${clientSeed}:${nonce}`)
.digest("hex");
}
function rollIndex(proofHash, totalWeight) {
return parseInt(proofHash.slice(0, 13), 16) % totalWeight;
}Verify after opening
After payment, buyers open their invoice and expand Show fairness proof on each case result. Selley marks outcomes Verified fair when all checks pass:
- Revealed server seed hashes to the pre-purchase commitment.
- Proof hash matches HMAC-SHA256(server seed, client seed + nonce).
- Roll index maps to the won item using the weight snapshot stored on the order line.
- Delivered code matches the second HMAC selection for that outcome's stock.
Buyers can copy the full proof bundle from the invoice. Merchants can fetch order details via GET /orders/:id or listen for the product:mystery:revealed webhook.
Pre-purchase checklist
- ✓Review drop table percentages and remaining stock
- ✓Copy the commitment hash and pool version (use Copy audit record on the product page)
- ✓Optionally set a personal client seed
- ✓After payment, verify SHA-256(revealed seed) equals your saved hash
Selley guarantees that stated odds were followed fairly — not that a specific rare item will drop. Low-chance items are rare because their weight is low, not because of hidden manipulation.
Pagination
List endpoints support cursor-based pagination. Pass limit to control page size and cursor from the previous response to fetch the next page. When nextCursor is null there are no more results.
| Name | Type | Description |
|---|---|---|
limit | integer | Items per page. Default 100, max 250. |
cursor | string | Opaque cursor returned as nextCursor in the previous response. |
# First page
curl "https://selley.io/api/v1/products?limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
# Next page using cursor
curl "https://selley.io/api/v1/products?limit=10&cursor=clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Rate limits
API requests may be rate-limited per API key. If you exceed the limit you will receive 429 Too Many Requests. Retry after the suggested delay. Webhook delivery has a 15-second timeout per request; failed deliveries are logged and can be retried from the dashboard.
Errors
Errors always return JSON with an error string. Validation errors may include an errors array with field-level details. Use the HTTP status code to determine the type of error.
| Status | Meaning |
|---|---|
| 400 | Bad Request – invalid parameters or missing required fields. |
| 401 | Unauthorized – missing or invalid API key. |
| 403 | Forbidden – the request is not allowed. |
| 404 | Not Found – the resource does not exist. |
| 409 | Conflict – e.g. duplicate coupon code. |
| 429 | Too Many Requests – rate limit exceeded. |
| 500 | Internal Server Error – retry later. |
{
"error": "Unauthorized"
}
// Validation error
{
"error": "code: String must contain at least 1 character(s)"
}Endpoints: /api/v1/products
/productsGET https://selley.io/api/v1/products
Returns a paginated list of your Live products, sorted by creation date descending.
Query Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Max items per page (default 100, max 250). |
cursor | string | Pagination cursor from previous response. |
Response
{
"products": [
{
"id": "clxabc123",
"productId": "rec_abc123",
"name": "Pro License",
"description": "One-year software license",
"image": "https://cdn.selley.io/img.jpg",
"type": "Serial",
"price": 29.99,
"currency": "USD",
"stock": 50,
"status": "Live",
"sortOrder": 0,
"unlisted": false,
"createdAt": "2024-06-01T10:00:00.000Z",
"updatedAt": "2024-06-01T10:00:00.000Z"
}
],
"nextCursor": "clxdef456"
}Request Example
curl "https://selley.io/api/v1/products?limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"/products/:idGET https://selley.io/api/v1/products/:id
Retrieve a single product by its internal ID or the shorter productId (e.g. rec_abc123).
URL Parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | Internal ID or productId of the product. |
Request Example
curl "https://selley.io/api/v1/products/rec_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"/productsPOST https://selley.io/api/v1/products
Create a new product. The product is created in Draft status by default. Set status to Live to make it visible on your storefront.
Request Body
| Name | Type | Description |
|---|---|---|
namerequired | string | Product title. |
pricerequired | number | Price in the specified currency. |
typerequired | string | Service | Digital | Physical | Serial | Subscription | InfoCard | Dynamic | MysteryBox |
description | string | Product description. |
currency | string | 3-letter currency code (default USD). |
stock | integer | null | Stock count. null = unlimited. |
status | string | Live | Draft | Archived. Default: Draft. |
unlisted | boolean | Hide from storefront but accessible via direct link. |
Request Example
curl -X POST "https://selley.io/api/v1/products" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro License",
"price": 29.99,
"type": "Serial",
"currency": "USD",
"status": "Live"
}'/products/:idPUT https://selley.io/api/v1/products/:id
Update any fields of an existing product. Only the fields you include will be changed.
Updatable Fields
| Name | Type | Description |
|---|---|---|
name | string | New product title. |
price | number | New price. |
status | string | Live | Draft | Archived |
stock | integer | null | New stock count. |
description | string | New description. |
unlisted | boolean | Hide/show on storefront. |
Request Example
curl -X PUT "https://selley.io/api/v1/products/rec_abc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"price": 39.99, "status": "Live"}'/products/:idDELETE https://selley.io/api/v1/products/:id
Permanently delete a product. This cannot be undone. Returns success: true on success.
{ "success": true }Request Example
curl -X DELETE "https://selley.io/api/v1/products/rec_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/orders
/ordersGET https://selley.io/api/v1/orders
Returns orders for your shop(s), most recent first. Includes order items. Scoped by shop (use X-Selley-Shop for multi-shop).
Query Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Max items (default 50, max 200). |
Response
{
"orders": [
{
"id": "clxabc123",
"orderNumber": "ORD-12345",
"status": "completed",
"paymentStatus": "paid",
"total": 29.99,
"currency": "USD",
"customerEmail": "[email protected]",
"customerName": "Jane Smith",
"paymentMethod": "Stripe",
"createdAt": "2024-06-01T10:00:00.000Z",
"paidAt": "2024-06-01T10:01:30.000Z",
"items": [
{
"id": "item_123",
"productId": "clx_prod",
"productName": "Pro License",
"quantity": 1,
"price": 29.99,
"total": 29.99
}
]
}
]
}Request Example
curl "https://selley.io/api/v1/orders?limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"/orders/:idGET https://selley.io/api/v1/orders/:id
Retrieve a single order by its internal ID or the display orderNumber (e.g. ORD-12345). Includes full item list.
| Name | Type | Description |
|---|---|---|
idrequired | string | Internal ID or orderNumber. |
curl "https://selley.io/api/v1/orders/ORD-12345" \
-H "Authorization: Bearer YOUR_API_KEY"/orders/:idPUT https://selley.io/api/v1/orders/:id
Update the status of an order. Useful for manually marking orders as completed or cancelled.
| Name | Type | Description |
|---|---|---|
status | string | pending | processing | completed | cancelled | refunded |
curl -X PUT "https://selley.io/api/v1/orders/ORD-12345" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "completed"}'Endpoints: /api/v1/coupons
/couponsGET https://selley.io/api/v1/coupons
Returns all discount coupons for your account. Filter to only active coupons with active=true.
Query Parameters
| Name | Type | Description |
|---|---|---|
active | boolean | Pass true to only return active coupons. |
Response
{
"coupons": [
{
"id": "clxabc123",
"code": "SAVE20",
"type": "percentage",
"value": 20,
"usageLimit": 100,
"usedCount": 3,
"minAmount": 10,
"expiresAt": "2025-12-31T23:59:59.000Z",
"active": true,
"createdAt": "2024-06-01T10:00:00.000Z",
"updatedAt": "2024-06-01T10:00:00.000Z"
}
]
}curl "https://selley.io/api/v1/coupons?active=true" \
-H "Authorization: Bearer YOUR_API_KEY"/coupons/:idGET https://selley.io/api/v1/coupons/:id
Retrieve a single coupon by its ID.
| Name | Type | Description |
|---|---|---|
idrequired | string | Coupon ID. |
curl "https://selley.io/api/v1/coupons/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"/couponsPOST https://selley.io/api/v1/coupons
Create a new coupon code. Supports percentage or fixed discounts, usage limits, and optional expiry dates.
Request Body
| Name | Type | Description |
|---|---|---|
coderequired | string | Coupon code (e.g. SAVE20). Automatically uppercased. |
typerequired | string | percentage | fixed |
valuerequired | number | Discount amount (% or currency unit). |
usageLimit | integer | null | Max redemptions. null = unlimited. |
minAmount | number | null | Minimum order total to apply coupon. |
expiresAt | string | null | ISO 8601 expiry date. null = no expiry. |
active | boolean | Whether the coupon is active. Default true. |
curl -X POST "https://selley.io/api/v1/coupons" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "SAVE20",
"type": "percentage",
"value": 20,
"usageLimit": 100,
"minAmount": 10,
"expiresAt": "2025-12-31T23:59:59.000Z"
}'/coupons/:idPUT https://selley.io/api/v1/coupons/:id
Update an existing coupon. Only the fields you provide will be modified.
| Name | Type | Description |
|---|---|---|
code | string | New coupon code. |
type | string | percentage | fixed |
value | number | New discount value. |
usageLimit | integer | null | New max redemptions. |
active | boolean | Enable or disable the coupon. |
expiresAt | string | null | New expiry date or null to remove. |
curl -X PUT "https://selley.io/api/v1/coupons/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"active": false}'/coupons/:idDELETE https://selley.io/api/v1/coupons/:id
Permanently delete a coupon. Returns success: true.
{ "success": true }curl -X DELETE "https://selley.io/api/v1/coupons/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/blacklists — shop-scoped
/blacklistsGET https://selley.io/api/v1/blacklists
List all blacklist entries (IP, country, email) for your shop. Blocked visitors are prevented from viewing your storefront.
{
"blacklists": [
{
"id": "clxabc123",
"type": "IP",
"value": "192.168.1.1",
"reason": "Fraud attempt",
"createdAt": "2024-06-01T10:00:00.000Z"
},
{
"id": "clxdef456",
"type": "COUNTRY",
"value": "RU",
"reason": null,
"createdAt": "2024-06-02T09:00:00.000Z"
}
]
}curl "https://selley.io/api/v1/blacklists" \
-H "Authorization: Bearer YOUR_API_KEY"/blacklists/:idGET https://selley.io/api/v1/blacklists/:id
Retrieve a single blacklist entry by ID.
| Name | Type | Description |
|---|---|---|
idrequired | string | Blacklist entry ID. |
curl "https://selley.io/api/v1/blacklists/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"/blacklistsPOST https://selley.io/api/v1/blacklists
Add a new entry to your shop's blacklist. type must be one of IP, COUNTRY, or EMAIL.
| Name | Type | Description |
|---|---|---|
typerequired | string | IP | COUNTRY | EMAIL |
valuerequired | string | The IP address, 2-letter country code, or email. |
reason | string | Optional internal note. |
curl -X POST "https://selley.io/api/v1/blacklists" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "IP",
"value": "1.2.3.4",
"reason": "Chargeback fraud"
}'/blacklists/:idPUT https://selley.io/api/v1/blacklists/:id
Update an existing blacklist entry's type, value, or reason.
| Name | Type | Description |
|---|---|---|
type | string | IP | COUNTRY | EMAIL |
value | string | New blocked value. |
reason | string | New reason note. |
curl -X PUT "https://selley.io/api/v1/blacklists/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Updated reason"}'/blacklists/:idDELETE https://selley.io/api/v1/blacklists/:id
Remove an entry from your blacklist. The visitor will no longer be blocked.
{ "success": true }curl -X DELETE "https://selley.io/api/v1/blacklists/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/categories
/categoriesGET https://selley.io/api/v1/categories
List all categories for your account, sorted by sortOrder then creation date. Includes bound products and groups.
{
"categories": [
{
"id": "clxabc123",
"name": "Software",
"sortOrder": 1,
"unlisted": false,
"products": [
{ "id": "...", "name": "Pro License", "productId": "rec_123" }
],
"groups": [
{ "id": "...", "name": "Starter Bundle" }
],
"createdAt": "2024-06-01T10:00:00.000Z"
}
]
}curl "https://selley.io/api/v1/categories" \
-H "Authorization: Bearer YOUR_API_KEY"/categories/:idGET https://selley.io/api/v1/categories/:id
Retrieve a single category with its bound products and groups.
| Name | Type | Description |
|---|---|---|
idrequired | string | Category ID. |
curl "https://selley.io/api/v1/categories/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"/categoriesPOST https://selley.io/api/v1/categories
Create a new category and optionally bind products and/or groups to it.
| Name | Type | Description |
|---|---|---|
namerequired | string | Category title. |
sortOrder | integer | Display order (lower = first). Default 0. |
unlisted | boolean | Hide from storefront. Default false. |
productIds | string[] | Array of product IDs to bind. |
groupIds | string[] | Array of group IDs to bind. |
curl -X POST "https://selley.io/api/v1/categories" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Software",
"sortOrder": 1,
"productIds": ["clxprod1", "clxprod2"]
}'/categories/:idPUT https://selley.io/api/v1/categories/:id
Update category fields. Passing productIds or groupIds replaces the full bound list.
| Name | Type | Description |
|---|---|---|
name | string | New category name. |
sortOrder | integer | New sort position. |
unlisted | boolean | Show or hide the category. |
productIds | string[] | Replaces all bound products. |
groupIds | string[] | Replaces all bound groups. |
curl -X PUT "https://selley.io/api/v1/categories/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Digital Goods", "sortOrder": 0}'/categories/:idDELETE https://selley.io/api/v1/categories/:id
Delete a category. Products and groups in the category are not deleted.
{ "success": true }curl -X DELETE "https://selley.io/api/v1/categories/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/groups
/groupsGET https://selley.io/api/v1/groups
List all product groups (bundles) for your account, including the products inside each group.
{
"groups": [
{
"id": "clxabc123",
"name": "Starter Bundle",
"description": "Best value pack",
"discountPercent": 15,
"sortOrder": 0,
"unlisted": false,
"products": [
{ "id": "...", "name": "...", "productId": "rec_1", "price": 9.99 }
],
"createdAt": "2024-06-01T10:00:00.000Z",
"updatedAt": "2024-06-01T10:00:00.000Z"
}
]
}curl "https://selley.io/api/v1/groups" \
-H "Authorization: Bearer YOUR_API_KEY"/groups/:idGET https://selley.io/api/v1/groups/:id
Retrieve a single group by ID, including its products.
| Name | Type | Description |
|---|---|---|
idrequired | string | Group ID. |
curl "https://selley.io/api/v1/groups/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"/groupsPOST https://selley.io/api/v1/groups
Create a new product group with an optional bundle discount. Bind products by passing productIds.
| Name | Type | Description |
|---|---|---|
namerequired | string | Group name. |
description | string | Optional description. |
discountPercent | number | Bundle discount percentage 0–100. |
image | string | Group image URL. |
sortOrder | integer | Display order. Default 0. |
unlisted | boolean | Hide from storefront. |
productIds | string[] | Product IDs to include in the group. |
curl -X POST "https://selley.io/api/v1/groups" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Starter Bundle",
"discountPercent": 15,
"productIds": ["clxprod1", "clxprod2"]
}'/groups/:idPUT https://selley.io/api/v1/groups/:id
Update group fields. Passing productIds replaces the group's product list.
| Name | Type | Description |
|---|---|---|
name | string | New name. |
discountPercent | number | New discount percentage. |
sortOrder | integer | New display order. |
unlisted | boolean | Show/hide. |
productIds | string[] | Replaces the full product list. |
curl -X PUT "https://selley.io/api/v1/groups/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"discountPercent": 20}'/groups/:idDELETE https://selley.io/api/v1/groups/:id
Delete a group. Products in the group are not deleted.
{ "success": true }curl -X DELETE "https://selley.io/api/v1/groups/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/customers — shop-scoped, read-only
/customersGET https://selley.io/api/v1/customers
List all customers who have placed orders in your shop. Sorted by creation date descending.
{
"customers": [
{
"id": "clxabc123",
"email": "[email protected]",
"name": "Jane Smith",
"phone": "+1234567890",
"country": "US",
"city": "New York",
"totalOrders": 5,
"totalSpent": 149.95,
"lastOrderAt": "2024-06-10T15:00:00.000Z",
"createdAt": "2024-01-05T08:00:00.000Z",
"updatedAt": "2024-06-10T15:00:00.000Z"
}
]
}curl "https://selley.io/api/v1/customers?limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"/customers/:idGET https://selley.io/api/v1/customers/:id
Retrieve a single customer by their ID including order stats and notes.
| Name | Type | Description |
|---|---|---|
idrequired | string | Customer ID. |
curl "https://selley.io/api/v1/customers/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/subscriptions — shop-scoped, read-only
/subscriptionsGET https://selley.io/api/v1/subscriptions
List all recurring subscriptions for your shop. Includes product and customer details.
{
"subscriptions": [
{
"id": "clxabc123",
"productId": "clx_prod",
"productName": "Pro Plan",
"productProductId": "rec_pro",
"customerId": "clx_cust",
"customerEmail": "[email protected]",
"status": "ACTIVE",
"stripeSubscriptionId": "sub_xxxxxxxxxx",
"stripeCustomerId": "cus_xxxxxxxxxx",
"currentPeriodEnd": "2024-07-01T00:00:00.000Z",
"createdAt": "2024-06-01T10:00:00.000Z",
"canceledAt": null
}
]
}curl "https://selley.io/api/v1/subscriptions" \
-H "Authorization: Bearer YOUR_API_KEY"/subscriptions/:idGET https://selley.io/api/v1/subscriptions/:id
Retrieve a single subscription by ID including Stripe IDs and period end date.
| Name | Type | Description |
|---|---|---|
idrequired | string | Subscription ID. |
curl "https://selley.io/api/v1/subscriptions/clxabc123" \
-H "Authorization: Bearer YOUR_API_KEY"Endpoints: /api/v1/themes
CLI & AI workflows
theme.json and custom.css — ideal for local development, CI, or AI-assisted theme editing./themesGET https://selley.io/api/v1/themes
Returns the active theme summary for your authenticated shop, including metadata for each theme slot in the bundle.
Response
{
"activeThemeId": "default",
"themes": [
{
"id": "default",
"name": "My Theme",
"owner": "merchant",
"version": "1.0.0",
"isActive": true,
"shopSlug": "my-shop",
"updatedAt": "2024-06-15T12:00:00.000Z"
}
]
}Request
curl "https://selley.io/api/v1/themes" \
-H "Authorization: Bearer YOUR_API_KEY"/themes/:themeIdGET https://selley.io/api/v1/themes/:themeId
Download the full theme bundle: normalized config plus file representation (theme.json and custom.css).
| Name | Type | Description |
|---|---|---|
themeIdrequired | string | Theme identifier (typically default). |
curl "https://selley.io/api/v1/themes/default" \
-H "Authorization: Bearer YOUR_API_KEY"Response fields
| Name | Type | Description |
|---|---|---|
id, name, owner, version | string | Theme metadata. |
isActive | boolean | Whether this theme is live on the storefront. |
shopSlug | string | Your shop slug for storefront URLs. |
config | object | Normalized theme configuration (colors, layout, components). |
files | object | CLI file bundle with theme.json and custom.css strings. |
updatedAt | string | ISO 8601 last update time. |
/themes/:themeIdPUT https://selley.io/api/v1/themes/:themeId
Push an updated theme. Send either a full config object or a files bundle — not both required, but at least one must be present.
Request body
| Name | Type | Description |
|---|---|---|
config | object | Full theme config object (normalized server-side). |
files | object | Partial file bundle: { "theme.json": {...}, "custom.css": "..." } |
Response
Returns the updated theme object (same shape as GET).
curl -X PUT "https://selley.io/api/v1/themes/default" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"files": {
"custom.css": ":root { --accent: #4de500; }"
}
}'Cache
Receive HTTP POST requests when events happen in your store
Overview
Webhooks send a POST request to a URL you provide whenever certain events occur (e.g. order paid, product updated, subscription renewed). Configure webhooks in the dashboard under Developers → Webhooks: add your endpoint URL and select which events to subscribe to. At least one event must be selected per webhook.
Each request includes a JSON body with event, data (detailed payload), timestamp, and source. Your endpoint should respond with 2xx to acknowledge receipt. Non-2xx responses are logged and may be retried.
Event types
Events are grouped by category. Select the events you need when creating a webhook. The data object structure depends on the event category (see Payload structure below).
Order
- order:created · order:updated · order:partial · order:paid · order:paid:product
- order:cancelled · order:disputed · order:cancelled:product · order:disputed:product
- order:created:product · order:partial:product · order:updated:product
Query
- query:created · query:replied
Feedback
- feedback:received
Product
- product:created · product:edited · product:stock · product:dynamic · product:mystery:revealed
Subscription
- subscription:trial:started · subscription:trial:started:product · subscription:created · subscription:created:product
- subscription:renewed · subscription:renewed:product · subscription:updated · subscription:updated:product
- subscription:cancelled · subscription:cancelled:product · subscription:trial:ended · subscription:trial:ended:product
- subscription:upcoming · subscription:upcoming:product
Request format
Every webhook is sent as POST to your URL with the following headers and body.
Headers
| Header | Value |
|---|---|
| Content-Type | application/json |
| User-Agent | Selley-Webhook/1.0 |
Top-level body fields
| Name | Type | Description |
|---|---|---|
eventrequired | string | Event identifier (e.g. order:paid, subscription:renewed). |
datarequired | object | Event-specific payload. Structure described in Payload structure sections below. |
timestamp | string | ISO 8601 time when the webhook was sent. |
source | string | Always "selley". |
Your endpoint should respond with HTTP 200–299 to acknowledge receipt. Timeout is 15 seconds. Failed or non-2xx deliveries are logged in Developers → Webhook Logs where you can retry or view the payload.
Payload structure
The data object in each webhook is detailed and depends on the event category. Below are the full structures for Order, Subscription, Product, Query, and Feedback. All monetary values use the order/product currency; timestamps are Unix seconds unless noted.
{
"event": "order:paid",
"data": { /* see Order data below */ },
"timestamp": "2024-06-15T14:32:00.000Z",
"source": "selley"
}Order data
For all order:* events, data includes order details, customer, product, gateway, fees, IP/location, status history, delivery info, and nested product block.
Identifiers & type: id, uniqid, type (PRODUCT), subtype, origin (e.g. EMBED).
Amounts: total, total_display, currency, exchange_rate, discount, fee_percentage, fee_breakdown, discount_breakdown.
Shop & customer: shop_id, name (shop name), customer_email, customer_id.
Product: product_id, product_title, product_type, quantity, subscription_id (null for one-time), subscription_time.
Metadata: custom_fields (object containing merchant-defined fields and values).
Gateway: gateway (STRIPE, PAYPAL, etc.), gateway_data, stripe_id / paypal_order_id etc.
Location & IP: country, location, ip, is_vpn_or_proxy, ip_info (ISP, city, region, device, browser, proxy/vpn/tor flags).
Status: status (e.g. COMPLETED), status_history, status_history_raw, created_at, updated_at.
Nested: product (full product object), products (array of line items), delivery_info, delivery, total_conversions (multi-currency/crypto).
{
"id": 238987,
"uniqid": "593fe6-bd6c461fa4-f713f6",
"type": "PRODUCT",
"origin": "EMBED",
"total": 45.77,
"total_display": "33.99",
"currency": "USD",
"shop_id": 5436,
"customer_email": "[email protected]",
"customer_id": "cst_xxx",
"product_id": "6894accbf05b6",
"product_title": "Semi-Annual Alpha Package",
"product_type": "SERVICE",
"quantity": 1,
"custom_fields": {
"Discord ID": "User#1234",
"Account Name": "AlphaUser"
},
"subscription_id": null,
"gateway": "STRIPE",
"status": "COMPLETED",
"discount": 0,
"fee_percentage": 4,
"country": "GB",
"ip": "2a00:23c7:...",
"ip_info": { "country_code": "GB", "city": "London", "ISP": "...", "vpn": 0, "proxy": 0 },
"created_at": 1772131826,
"updated_at": 1772131943,
"status_history": [...],
"gateway_data": { "type": "STRIPE", "label": "Stripe", "payment_reference": "pi_xxx" },
"product": { "uniqid": "...", "title": "...", "price_display": "...", "currency": "...", ... },
"products": [{ "uniqid": "...", "title": "...", "quantity": 1, "price": 45.77, "currency": "USD" }],
"delivery_info": { "downloads": [], "license_keys": [], "external_urls": [...], "tracking_codes": [] },
"total_conversions": { "USD": 45.77, "EUR": "39.38", "crypto": { "BTC": "...", "ETH": "..." } }
}Subscription data
For subscription:* events, data includes which product, amount, billing interval, subscription and Stripe IDs, period dates, trial info, and customer.
| Name | Type | Description |
|---|---|---|
id / uniqid | string | Subscription identifier. |
shop_id, shop_name | number / string | Shop context. |
product_id, product_uniqid, product_title, product_type | string | Product the subscription is for. |
customer_id, customer_email | string | Subscriber. |
subscription_id, stripe_subscription_id, stripe_price_id | string | Subscription and Stripe references. |
subscription_status | string | ACTIVE, TRIALING, CANCELLED, etc. |
amount, amount_display, currency, quantity | number / string | Recurring amount and quantity. |
billing_interval, recurring_interval_count | string / number | e.g. MONTH, 1. |
current_period_start, current_period_end | number | Unix timestamps for current period. |
trial_start, trial_end | number | null | Trial period (if applicable). |
gateway, country, ip, ip_info | string / object | Payment gateway and location. |
created_at, updated_at, canceled_at | number | null | Timestamps. |
product | object | Full product block. |
Product data
For product:created, product:edited, product:deleted, product:stock, product:stock:low, product:dynamic, data includes product details, pricing, stock, gateways, and metadata.
| Name | Type | Description |
|---|---|---|
id, uniqid | string | Product identifier. |
shop_id, title, description, product_type | string | Shop and product info. |
price, price_display, currency | number / string | Pricing. |
quantity_min, quantity_max, stock, stock_warning | number | Quantity and stock levels. |
gateways | string[] | Enabled gateways (e.g. STRIPE, PAYPAL). |
image_name, image_storage, unlisted, sort_priority | string / number / boolean | Media and visibility. |
volume_discounts | array | e.g. [{ type: PERCENTAGE, value: 5, quantity: 10 }]. |
created_at, updated_at | number | Unix timestamps. |
Mystery box data
For product:mystery:revealed, Selley fires after provably fair rolls complete for a mystery box line item. The webhook data identifies the order and product; full outcome details (rolled item, commitment hash, server seed, roll proof) are stored on the order item and returned on the customer invoice and order APIs.
| Name | Type | Description |
|---|---|---|
orderIdrequired | string | Internal order ID. |
orderNumberrequired | string | Customer-facing order number (e.g. ORD-12345). |
productIdrequired | string | Mystery box product ID. |
productNamerequired | string | Product title at time of purchase. |
Full reveal data
GET /orders/:id or load the invoice page to access per-quantity outcomes with verification fields. Customers verify fairness from the invoice reveal panel without needing API access.{
"event": "product:mystery:revealed",
"data": {
"orderId": "clxord123",
"orderNumber": "ORD-48291",
"productId": "rec_mystery01",
"productName": "Premium Case"
},
"timestamp": "2024-06-15T14:32:00.000Z",
"source": "selley"
}Dynamic fulfillment data
The INVOICE.ITEM.DELIVER-DYNAMIC event is sent to the Dynamic Webhook URL configured on individual dynamic products. This payload is extremely rich and provides full context for the order, the customer (including custom fields), and the specific line item.
Identifiers: id (Order ID), order_number, unique_id, shop_customer_id.
Customer: Linked customer record including email, name, and extracted discord_id / discord_username if available from custom fields.
Coupon: Applied coupon details with code, discount value, and type.
Item: The specific product instance with price, quantity, and its own custom_fields block.
{
"event": "INVOICE.ITEM.DELIVER-DYNAMIC",
"id": "ord_238987",
"order_number": "ORD-SELLEY-1234",
"coupon_id": "cpn_555",
"coupon_discount": "5.00",
"gateway_fee": "0.45",
"shop_customer_id": "cst_888",
"created_at": "2024-06-15T14:32:00.000Z",
"updated_at": "2024-06-15T14:32:10.000Z",
"gateway": "STRIPE",
"email": "[email protected]",
"status": "completed",
"price": "45.00",
"currency": "USD",
"amount": 1,
"custom_fields": {
"Discord ID": "User#0001",
"Special Instructions": "Please deliver via API"
},
"customer": {
"id": "cst_888",
"email": "[email protected]",
"name": "John Doe",
"discord_id": "000000000000000000",
"discord_username": "john_doe"
},
"coupon": {
"id": "cpn_555",
"code": "SAVE5",
"discount": "5.00",
"type": "fixed"
},
"item": {
"id": "itm_999",
"invoice_id": "ord_238987",
"product_id": "prod_777",
"status": "completed",
"price": "45.00",
"quantity": 1,
"total_price": "45.00",
"custom_fields": { ... },
"product": {
"id": "prod_777",
"name": "Dynamic Service X"
}
}
}Query & Feedback data
Query (query:created, query:replied): data includes id, ticket_id, shop_id, customer_email, customer_id, subject, message, status, reply_count, created_at, updated_at.
Feedback (feedback:received): data includes id, shop_id, product_id, product_title, order_id, customer_email, rating, comment, created_at.
Logs & Simulator
In the dashboard, Developers → Webhook Logs shows every delivery attempt: URL, event, response status, retry count, and created time. You can Send again to retry a failed delivery or open Payload to view the exact JSON that was sent.
Use the Simulator to send a test webhook to a configured URL: choose a webhook and an event, then trigger a delivery. The payload will match the structure documented above (with sample data). This helps verify your endpoint and payload handling without creating real orders or subscriptions.
Open Webhook Logs & SimulatorAdd buy buttons and checkout to any site
Overview
The Embed lets you place a product or bundle (group) checkout on any external website. Customers see your product, choose quantity, and complete payment without leaving the host site. The flow is a modal that opens from your embed script; checkout and payment happen inside the modal.
Generate your embed code in the dashboard under Developers → Embed: select a product or group, optionally set button text and styles, then copy the script and HTML snippet. Add them to your site where you want the button to appear.
Open Embed generatorUsage
Include the Selley embed script on your page, then add a button or link with data-selley-store, and for a single product data-selley-product (product ID from your store), or for a bundle data-selley-group (group ID). When the user clicks, a modal opens with the product/group details and a gateway selection that leads to full checkout. No redirect to Selley is required; the flow stays in the modal.
Example: single product
<script src="https://your-domain.com/embed.js" async></script>
<button data-selley-store="your-store" data-selley-product="rec_abc123">
Buy now
</button>Example: bundle (group)
<button data-selley-store="your-store" data-selley-group="group_xyz">
Buy bundle
</button>Your store subdomain (e.g. your-store.myselley.com) or custom domain is used as the base. The embed script and CSS URL are shown in the dashboard Embed page.