Demo. Payments here are simulated, invoices settle by themselves after a few seconds. Never send real coins to an address on this site.

read this first

Documentation

Everything needed to take a shop from empty to selling, and to automate it afterwards.

Getting started

  1. Create an account and name your shop. The slug becomes your storefront address at /s/<slug>; you can point your own domain at it later.
  2. Add a wallet. Paste the account-level extended public key from your own wallet for each asset you want to accept. Check the three preview addresses against your wallet before you save.
  3. Add a product. Pick a delivery type, set a price, and, for serial products, paste your stock.
  4. Take a test order. On a development install with CRYPTO_PROVIDER=mock, open /dev/pay and settle your own invoice. The whole flow runs without touching a blockchain.

Wallets and xpubs

Shuz is non-custodial. It stores an extended public key and derives one receive address per invoice from it. An extended public key can generate addresses; it cannot sign a transaction. There is no private key on the server, which is why nobody running this software can move your money.

Export the account-level key, not the master key:

AssetPathPrefix
Bitcoinm/84'/0'/0'zpub or xpub
Litecoinm/84'/2'/0'Mtub or Ltub
Ethereum, USDT, USDCm/44'/60'/0'xpub

The three Ethereum-family assets share one key and one address per invoice, the watcher tells them apart by what actually arrived, not by where it arrived.

Never paste a key beginning xprv, zprv or Ltpv. Those are private keys. Shuz refuses them, but no software can protect a key you have already pasted somewhere else.

Product types

TypeWhat the buyer receives
SERIALOne entry from your stock pool per unit, reserved at checkout so it cannot be sold twice.
FILEA signed, expiring download link per attached file, with a per-order download cap.
DYNAMICWhatever your own endpoint returns when we call it with a signed request.
SERVICENothing automatic, the line lands in your fulfilment queue with the buyer's details.
INFOA fixed text payload: an invite link, a template, a set of instructions.
SUBSCRIPTIONAccess for a period, renewed by a fresh invoice raised before it lapses.

The order lifecycle

PENDING      invoice issued, nothing seen on chain
PARTIAL      some value arrived, below the tolerance floor
CONFIRMING   enough value seen, waiting for confirmations
COMPLETED    paid and every line delivered
AWAITING_FULFILMENT
             paid, but a line needs you (a service, or a failed delivery)
EXPIRED      never paid inside the window; stock released
REFUNDED     you sent the coins back and recorded it

Fulfilment is per line, not per order. A basket holding a key and a bespoke service delivers the key immediately and leaves the service in your queue.

REST API

Create an API key under Shop → API keys. It is shown once. Authenticate with a bearer token:

curl https://your-shop.example/api/v1/products \
  -H "Authorization: Bearer shuz_live_…"

Keys are scoped (products:read, orders:write, and so on) and rate limited per key. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; a rejected request carries Retry-After. The full schema is published at /api/v1/openapi.json.

Creating an order returns the payment address to show your buyer:

POST /api/v1/orders
{
  "email": "buyer@example.com",
  "asset": "BTC",
  "lines": [{ "productId": "…", "quantity": 1 }]
}

201
{
  "order":   { "number": "4KQ7-M2XN-9F3T", "status": "PENDING" },
  "invoice": { "address": "bc1q…", "expected": "0.00031312", "expiresAt": "…" }
}

Webhooks

Add an endpoint under Shop → Webhooks and subscribe it to the events you care about. Each delivery carries:

X-Shuz-Event:     order.completed
X-Shuz-Delivery:  <delivery id>
X-Shuz-Timestamp: 1774000000
X-Shuz-Signature: <hex>

The signature is HMAC-SHA256(timestamp + "." + rawBody) using your endpoint's secret. Verify it before trusting the body, and reject a timestamp more than a few minutes old so a captured request cannot be replayed:

import { createHmac, timingSafeEqual } from 'node:crypto'

const expected = createHmac('sha256', secret)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex')

const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(signature))

Failed deliveries are retried with an exponential backoff. An endpoint that fails ten times in a row is disabled automatically and you are told about it.

Licence validation

Enable licensing on a product and every unit sold issues a key. Your own software checks it against a public endpoint, no API key, because it runs on your customer's machine:

POST /api/v1/license/validate
{ "key": "A1B2C-D3E4F-G5H6J-K7L8M", "hardwareId": "…" }

{ "valid": true, "status": "ACTIVE",
  "expiresAt": null, "activationsRemaining": 2 }

Activations are bound to the hardware id you supply, counted against the product's limit, and revocable from the dashboard. A failed check never says which shop or product the key belongs to.