NIP-15 is Nostr’s marketplace standard: a merchant publishes a stall (kind 30017) and products (kind 30018) as replaceable events, then settles orders over encrypted direct messages with Lightning, LNURL, on-chain, or an external invoice. It is powerful but now flagged “unrecommended” in favour of NIP-99. Here is how it works and where it fits.
For a sovereign Bitcoiner, the appeal is obvious: a storefront with no platform account, no KYC gate, and no single company that can delete your listings. Your identity is an npub, your catalogue lives on relays you choose, and payment lands in a wallet you control. This guide walks through the actual Nostr events behind a NIP-15 stall, the order handshake, the client landscape, and the honest trade-offs against a self-hosted BTCPay Server plus WooCommerce store. None of this is a pitch for one rail over another — it is a map so you can pick the right tool.
What NIP-15 actually is
NIP-15 (“Nostr Marketplace”) is one of the Nostr Improvement Proposals maintained at github.com/nostr-protocol/nips. It defines a small set of event kinds that together describe a shop and move an order from cart to confirmation. The heavy lifting is done with parameterized replaceable events — events whose latest version for a given identifier overwrites the previous one, so editing a price or stock level is just publishing a newer event with the same d tag.
The core kinds are:
30017—set_stall: creates or updates a merchant “stall” (a shop), including its currency and shipping zones.30018—set_product: creates or updates a single product, tied to a stall.30019— marketplace UI: lets a marketplace front-end customise appearance and group merchants.30020— auction product: a product sold by bidding, with bids carried as kind1021and merchant bid confirmations as kind1022.
Because stalls and products are events signed by the merchant’s key, they are portable. The same stall can be read by any compatible client, mirrored across many relays, and verified as authentically yours — no marketplace “account” sits between you and the buyer. That portability is the whole point of building commerce on Nostr rather than on a hosted platform.
Inside a stall and a product event
The content field of each event is a JSON string. A stall (kind 30017) carries the shop’s identity and how it ships. A product (kind 30018) references its parent stall and describes one item. The table below lists the fields the spec defines.
| Event | Field | Meaning |
|---|---|---|
Stall 30017 |
id |
Merchant-generated stall identifier (mirrored in the event’s d tag) |
| Stall | name / description |
Shop name and optional blurb |
| Stall | currency |
Required pricing currency for the stall (e.g. an ISO code or a sat denomination) |
| Stall | shipping |
Array of zones, each with id, name, cost, and a list of regions |
Product 30018 |
id / stall_id |
Product identifier (in the d tag) and the stall it belongs to |
| Product | name / description |
Item title and details |
| Product | images |
Optional array of image URLs (hosted off-relay) |
| Product | currency / price |
Price expressed in the stated currency |
| Product | quantity |
Integer in stock, or null for unlimited / made-to-order |
| Product | specs |
Optional key/value pairs, e.g. [["color","black"],["weight","1.2 kg"]] |
| Product | shipping |
Optional per-product shipping cost adjustments layered on the stall zones |
Two tagging rules matter. The d tag must equal the stall or product id — that is what makes the event replaceable. And products can carry t tags for categories, which is how a marketplace UI builds browse-by-category views. Note the quantity semantics carefully: relays are eventually consistent, so “stock” is a best-effort number the merchant republishes, not a transactional ledger. For a build-to-order shop selling hand-assembled hardware, null quantity is often the honest answer.
The order and payment handshake
Listings are public; the checkout is private. NIP-15 moves the order over encrypted direct messages — historically NIP-04 DMs (kind 4), though most current clients have moved to the gift-wrapped, metadata-leaking-resistant NIP-17 private messages. Either way, the payload is a JSON object with a type field that drives a three-step exchange:
- Type 0 — customer order. The buyer sends an
id, theirnameandaddress, an optionalmessage, contact details (nostr,phone,email), anitemsarray ofproduct_id+quantity, and the chosenshipping_id. - Type 1 — merchant payment request. The seller replies with the order
id, amessage, and apayment_optionsarray. Each option has atypeand alink. - Type 2 — order status update. The seller confirms with the order
id, amessage, and two booleans:paidandshipped.
The payment_options array is where NIP-15 plugs into the wider Bitcoin stack. The defined option types are:
type |
What the link contains |
|---|---|
ln |
A Lightning (BOLT11) invoice for the order total |
lnurl |
An LNURL-pay endpoint that produces an invoice on demand |
btc |
An on-chain Bitcoin address |
url |
An external checkout page — for example a BTCPay Server invoice, or a third-party processor |
This is deliberately minimal. NIP-15 does not run an escrow, does not hold funds, and does not arbitrate disputes. It standardises the conversation; settlement happens on whatever rail the merchant offers. That keeps the protocol non-custodial by default, but it also means refunds, partial shipments, and disputes are handled human-to-human over DMs — there is no platform “open a case” button.
The “unrecommended” label and the move to NIP-99
Read the spec today and you will see a candid warning at the top: NIP-15 is marked unrecommended, described as “too complicated,” with a pointer to NIP-99 instead. This is the single most important thing to know before building on it. The marketplace primitives still work and clients still support them, but the ecosystem’s centre of gravity is shifting.
NIP-99 (“Classified Listings,” kind 30402) takes a simpler view: each listing is a self-contained classified ad rather than a structured stall-and-product graph. It interoperates more cleanly across general Nostr clients, because a listing is just another readable event, not a bespoke shop schema. The cost is that you lose some of NIP-15’s structure (explicit stalls, shipping zones, the typed order handshake) and rebuild more of the checkout logic yourself or rely on your client to provide it.
Practically: if you are choosing a stack in 2026, treat NIP-15 as a working but legacy standard and check what your chosen client uses under the hood. Several have already migrated to, or layered on, NIP-99. The good news for a merchant is that this is mostly an implementation detail — your npub identity, your relays, and your Lightning payout wallet carry over regardless of which listing kind wins.
The client landscape
You almost never hand-craft these events yourself; a client does it. Three projects are worth knowing, each open source and credited to the Nostr builders who pushed merchant commerce forward.
Shopstr (GPL-3.0, github.com/shopstr-eng/shopstr, live at shopstr.market) bills itself as a global, permissionless Nostr marketplace for Bitcoin commerce. Its README lists a broad NIP set including NIP-99 classified listings, NIP-17 private messages, NIP-57 zaps, and notably NIP-60/NIP-61 — a built-in Cashu ecash wallet and “nutzaps.” So Shopstr leans toward Lightning and Cashu settlement and represents where the spec has evolved past raw NIP-15.
Plebeian Market (GPL-3.0, PlebeianTech on GitHub) is the “self-sovereign marketplace” angle. It is built on NIP-15, pays over Lightning, and — the part sovereign builders care about — is self-hostable. You can run your own instance on a VPS or at home on an Umbrel or Start9 node, which makes the marketplace itself something you operate rather than merely use. Their model routes Lightning payments through an Alby wallet to the seller, with an optional value-4-value contribution split. Their stated vision is many small community-run marketplaces — “the mycelium of free commerce.”
LNbits nostrmarket (github.com/lnbits/nostrmarket) is an extension for the LNbits stack that implements the NIP-15 merchant side. If you already run LNbits on your node, it lets your existing Lightning backend act as the stall’s order and invoice engine. It is the most “bolt onto my node” option of the three.
The honest read: this is a young, fast-moving niche. Clients come and go, schemas are migrating, and relays vary in reliability. Test with small, low-stakes listings before you trust it with your main catalogue, and keep your product source-of-truth somewhere you control.
Wiring up payments: Lightning, zaps, NWC, and BTCPay
NIP-15’s payment options map directly onto tools you may already run. The ln and lnurl options are satisfied by any Lightning node or LNURL service; the btc option by any wallet that can hand you a fresh address; and the url option by a hosted invoice page.
That url option is the natural bridge to BTCPay Server. BTCPay is self-hosted, non-custodial invoice infrastructure: it generates a checkout page, watches for Lightning or on-chain payment, exposes the Greenfield API for automation, and gives you receipts, accounting exports, and refund tooling that bare NIP-15 messaging lacks. A merchant can answer a Type 1 payment request with a url pointing at a BTCPay invoice and get fulfilment-grade bookkeeping while still selling through a Nostr stall. It is a clean separation of concerns — Nostr for discovery and the order conversation, BTCPay for settlement and records.
Two more pieces round out a sovereign setup. Zaps (NIP-57) are not a checkout mechanism but are useful for tips, value-4-value, and bootstrapping a reputation around your npub. And Nostr Wallet Connect (NIP-47) lets a client request invoices from, or pay through, your own node without handing over keys — the same remote-signing-style pattern that keeps custody with you. If you are not already running it, our Nostr Wallet Connect guide covers how that handshake works and why it matters for keeping payments under your control.
When a Nostr stall makes sense — and when a self-hosted store does
This is the question that actually matters, and the answer is “it depends on your threat model and your catalogue.” Both rails are legitimate; neither is universally better. A NIP-15 stall and a self-hosted WooCommerce + BTCPay store optimise for different things.
| Dimension | NIP-15 Nostr stall | Self-hosted WooCommerce + BTCPay |
|---|---|---|
| Censorship resistance | High — no platform account; listings replicate across relays you choose | Medium — you own the box, but domain/DNS/host can still be pressured |
| Identity | Portable npub, usable across clients; no KYC to list | Tied to your domain and hosting; brand-anchored |
| Setup effort | Low to start; pick a client, publish a stall | Higher — you run and patch the full stack |
| Catalogue / inventory | Best-effort, eventually consistent across relays | Authoritative database with real stock, variants, SKUs |
| Tax, shipping, refunds | Manual, DM-driven; little automation | Mature plugins for tax tables, carriers, returns |
| Discovery / SEO | Fragmented across clients and relays; no Google footprint | Full search-engine indexing and on-page control |
| Payments / custody | Non-custodial by design (Lightning/LNURL/on-chain/BTCPay) | Non-custodial via BTCPay; you settle to your own wallet |
| Spec stability | In flux — NIP-15 deprecating toward NIP-99 | Stable, long-lived, widely documented |
| Buyer friction | Buyer needs a Nostr client and some literacy | Familiar web checkout anyone can use |
So a Nostr stall earns its keep when censorship resistance and permissionless listing are the priority, when your buyers are already Nostr-native, when the catalogue is small or digital, and when manual, conversational fulfilment is acceptable. A self-hosted WooCommerce + BTCPay store earns its keep when you need authoritative inventory, real tax and shipping automation, search-engine discoverability, and a checkout your least-technical customer can complete — while still keeping self-custody of every sat through BTCPay. Most serious merchants will recognise their own shop in the right-hand column for day-to-day volume.
A pragmatic hybrid for sovereign merchants
These rails are not mutually exclusive, and the most resilient posture uses both. Keep your authoritative catalogue, inventory, and accounting on a stack you run — a self-hosted store settling through BTCPay — and treat a Nostr stall as a censorship-resistant secondary shopfront and reach channel. List a curated subset on Nostr, point the NIP-15 url payment option back at your BTCPay invoices, and you get the discoverability and operational maturity of a real store plus a relay-replicated presence no single platform can switch off.
That layered thinking is the same principle behind everything in our sovereignty work and the broader case for self-hosting your own infrastructure: each tool removes one more dependency on permission. NIP-15 will not replace a well-run store, and it does not need to. It adds a sovereign escape hatch — and credit for that belongs to fiatjaf and the Nostr contributors, the Shopstr and Plebeian Market teams, the BTCPay maintainers, and the Lightning and Cashu developers whose work it stitches together.
Frequently asked questions
Is NIP-15 still safe to build on in 2026?
It works and clients still support it, but the spec itself is now marked “unrecommended” and points builders to NIP-99. If you start today, verify which listing standard your chosen client actually uses and expect NIP-99 to keep gaining ground. Your npub identity, relays, and payout wallet carry over either way, so the migration risk is mostly an implementation detail rather than a lock-in.
Do I have to give up self-custody to sell on Nostr?
No. NIP-15 never holds your funds. The payment options resolve to a Lightning invoice, an LNURL endpoint, an on-chain address, or an external page such as a BTCPay invoice — all of which can settle directly to a wallet or node you control. The protocol standardises the order conversation, not custody, so keeping your keys is entirely up to your setup.
What is the difference between NIP-15 and NIP-99?
NIP-15 models a structured shop: a stall (kind 30017) with products (kind 30018) and a typed order/payment/status message flow. NIP-99 (kind 30402) treats each listing as a single self-contained classified ad, which is simpler and interoperates better across general Nostr clients. NIP-99 trades some of NIP-15’s built-in structure for broader compatibility, which is why several clients have migrated.
How does BTCPay Server fit with a Nostr marketplace?
BTCPay slots into the NIP-15 url payment option. The merchant answers a buyer’s order with a link to a self-hosted, non-custodial BTCPay invoice that watches for Lightning or on-chain payment and provides receipts, refunds, and accounting exports. You get Nostr for discovery and the order handshake, and BTCPay for fulfilment-grade settlement and bookkeeping.
Should I replace my WooCommerce store with a Nostr stall?
For most merchants, no — they complement each other. A self-hosted store gives you authoritative inventory, tax and shipping automation, and search-engine discoverability that a relay-based stall cannot match yet. A Nostr stall adds censorship resistance and permissionless reach. A common pattern is to keep the store as the source of truth and run a Nostr stall as a secondary, harder-to-deplatform shopfront that settles through the same BTCPay backend.
Can buyers pay without a Nostr account?
To place a NIP-15 order, the buyer generally needs a Nostr client because the checkout runs over encrypted direct messages tied to their key. That is real friction compared with a familiar web checkout. If your audience is not Nostr-native, a conventional storefront will convert better; reserve the Nostr stall for buyers who already live in the ecosystem or who specifically value the censorship resistance.



