12<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-15 - Nostr Marketplace — Nostr Instruction Manual</title>7<meta name="description" content="NIP-15: stalls, products, checkout messages, and auctions for ecommerce over Nostr. Deprecated in favor of NIP-99. Complete manual page.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 15 - Commerce</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="15">15</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 15</div>50 <h1>Nostr Marketplace</h1>51 <p class="sub">Stalls, products, checkout messages, and auctions for building an ecommerce marketplace on top of relays.</p>52 </div>53 <div class="stamp">Deprecated</div>54 </div>5556 <main class="spread">57 <div class="main">5859 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>60 <p>NIP-15 defines a full ecommerce protocol. A <strong>merchant</strong> lists a shop as a set of Nostr events. A <strong>customer</strong> browses that shop, places an order, pays, and gets shipping updates, all through signed events and encrypted direct messages.</p>61 <p>The design is based on <a href="https://github.com/lnbits/Diagon-Alley">Diagon-Alley</a>, an early lnbits shop plugin. Two open-source projects implement it: <a href="https://github.com/lnbits/nostrmarket">NostrMarket</a> and <a href="https://github.com/PlebeianTech/plebeian-market">Plebeian Market</a>.</p>62 <p>A merchant publishes a <strong>stall</strong>: a named list of products with a currency and shipping zones. Inside a stall, the merchant publishes individual <strong>products</strong> for sale. A <strong>marketplace</strong> client subscribes to one or more merchant public keys, lists their stalls and products, and gives the customer a basket and a checkout flow, much like an ordinary online shop.</p>6364 <div class="box danger">65 <span class="tag">Deprecated</span>66 <p>NIP-15 is <strong>unrecommended</strong>. The spec itself calls it too complicated. Use <a href="99.html">NIP-99</a>, Classified Listings, instead for new work. Read this chapter to understand deployed marketplaces built on NIP-15, not to build new ones.</p>67 </div>6869 <h3>Two Kinds of Client</h3>70 <p>NIP-15 describes two separate pieces of software:</p>71 <div class="tscroll"><table class="ref">72 <caption>Client roles</caption>73 <tr><th>Client</th><th>Used by</th><th>Job</th></tr>74 <tr><td class="btn">Merchant admin</td><td>merchant</td><td>Create, update, and delete stalls and products. Manage sales, payments, and messages with customers. Can be client-only, but a always-on server component helps catch orders while the merchant is offline.</td></tr>75 <tr><td class="btn">Marketplace</td><td>customer</td><td>Browse stalls and products from one or more merchants. Hold a basket. Run checkout. Entirely client-side: a stand-alone app or a plain front-end webpage.</td></tr>76 </table></div>7778 <figure class="diagram">79 <div class="frame">80 <svg viewBox="0 0 560 190" role="img" aria-label="Merchant admin publishes stall and product events to relays; a marketplace client subscribes and lists them for the customer">81 <rect class="d-actor" x="10" y="70" width="120" height="50" rx="8"/>82 <text class="d-actor-label" x="70" y="93" text-anchor="middle">Merchant</text>83 <text class="d-note" x="70" y="110" text-anchor="middle">admin client</text>84 <rect class="d-actor" x="220" y="70" width="120" height="50" rx="8"/>85 <text class="d-actor-label" x="280" y="99" text-anchor="middle">Relays</text>86 <rect class="d-actor" x="430" y="70" width="120" height="50" rx="8"/>87 <text class="d-actor-label" x="490" y="93" text-anchor="middle">Marketplace</text>88 <text class="d-note" x="490" y="110" text-anchor="middle">customer client</text>89 <path class="d-msg d-flow" d="M132,88 C165,82 185,82 218,88"/>90 <text class="d-label" x="175" y="72" text-anchor="middle">30017 / 30018</text>91 <path class="d-msg blue d-flow" d="M342,100 C375,106 395,106 428,100"/>92 <text class="d-label" x="385" y="130" text-anchor="middle">REQ kinds 30017, 30018</text>93 <circle r="4" fill="#f26a00">94 <animateMotion dur="2.4s" repeatCount="indefinite" path="M132,88 C165,82 185,82 218,88"/>95 </circle>96 <circle r="4" fill="#2e5f9e">97 <animateMotion dur="2.4s" begin="1.2s" repeatCount="indefinite" path="M342,100 C375,106 395,106 428,100"/>98 </circle>99 </svg>100 </div>101 <figcaption>Fig. 15-1 — <b>Shop architecture.</b> The merchant admin publishes stall and product events. Any marketplace client can subscribe and build a storefront from them.</figcaption>102 </figure>103104 <h2 class="bar" id="spec"><span class="num">2</span> Roles and Vocabulary</h2>105 <p>This chapter uses five terms. Learn them before reading the event formats.</p>106 <div class="tscroll"><table class="ref">107 <caption>NIP-15 terms</caption>108 <tr><th>Term</th><th>Meaning</th></tr>109 <tr><td class="btn">merchant</td><td>Seller of products. Identified by a Nostr keypair.</td></tr>110 <tr><td class="btn">customer</td><td>Buyer of products. Identified by a Nostr keypair.</td></tr>111 <tr><td class="btn">product</td><td>One item for sale, listed by a merchant.</td></tr>112 <tr><td class="btn">stall</td><td>A list of products controlled by one merchant. A merchant may run several stalls.</td></tr>113 <tr><td class="btn">marketplace</td><td>Client-side software that searches stalls and lets a customer buy products.</td></tr>114 </table></div>115116 <h2 class="bar"><span class="num">3</span> Merchant Events</h2>117 <p>A merchant publishes five kinds of event. Each one plays a distinct role in the shop:</p>118 <div class="tscroll"><table class="ref">119 <caption>Merchant event kinds</caption>120 <tr><th>Kind</th><th>Name</th><th>Description</th></tr>121 <tr><td class="btn">0</td><td>set_meta</td><td>The merchant's profile metadata. Same kind-0 event every Nostr user has.</td></tr>122 <tr><td class="btn">30017</td><td>set_stall</td><td>Create or update a stall. Addressable event.</td></tr>123 <tr><td class="btn">30018</td><td>set_product</td><td>Create or update a product. Addressable event.</td></tr>124 <tr><td class="btn">4</td><td>direct_message</td><td>Talk to the customer. Content is plain text or a checkout JSON message. See §6.</td></tr>125 <tr><td class="btn">5</td><td>delete</td><td>Delete a product or a stall. The regular <a href="09.html">NIP-09</a> deletion request.</td></tr>126 </table></div>127 <p>Kinds 30017 and 30018 sit in the addressable range. A relay keeps only the latest event for a given merchant pubkey, kind, and <code>d</code> tag value. Publishing a new version with the same <code>d</code> tag replaces the old one.</p>128129 <h2 class="bar" id="stall"><span class="num">4</span> Stall Event (kind 30017)</h2>130 <p>A stall groups products under one name, one currency, and one set of shipping zones.</p>131 <div class="crt">132 <div class="titlebar">Event content - kind 30017</div>133<pre>{134 <span class="k">"id"</span>: <span class="c"><string, merchant-generated stall id. Avoid sequential ids like "0", "1", "2"></span>135 <span class="k">"name"</span>: <span class="c"><string, stall name></span>136 <span class="k">"description"</span>: <span class="c"><string, optional, stall description></span>137 <span class="k">"currency"</span>: <span class="c"><string, currency used by this stall></span>138 <span class="k">"shipping"</span>: [139 {140 <span class="k">"id"</span>: <span class="c"><string, merchant-generated shipping zone id></span>141 <span class="k">"name"</span>: <span class="c"><string, optional, zone name></span>142 <span class="k">"cost"</span>: <span class="c"><float, base shipping cost in the stall currency></span>143 <span class="k">"regions"</span>: [<span class="c"><string, region names covered by this zone></span>]144 }145 ]146}</pre>147 </div>148 <div class="tscroll"><table class="ref">149 <caption>Stall content fields</caption>150 <tr><th>Field</th><th>Type</th><th>Rule</th></tr>151 <tr><td class="btn">id</td><td>string</td><td>Merchant-chosen. Sequential ids are discouraged.</td></tr>152 <tr><td class="btn">name</td><td>string</td><td>Stall name shown to customers.</td></tr>153 <tr><td class="btn">description</td><td>string</td><td>Optional. Free text.</td></tr>154 <tr><td class="btn">currency</td><td>string</td><td>Applies to the stall and to every product in it.</td></tr>155 <tr><td class="btn">shipping</td><td>array of zone objects</td><td>Possible shipping zones. The customer must pick exactly one at checkout.</td></tr>156 <tr><td class="btn">shipping[].id</td><td>string</td><td>Internal value chosen by the merchant. The customer's checkout order must send this id back unchanged.</td></tr>157 <tr><td class="btn">shipping[].cost</td><td>float</td><td>Base cost for shipping to this zone. Use zero for goods with no shipping cost, such as digital items.</td></tr>158 <tr><td class="btn">shipping[].regions</td><td>array of strings</td><td>The regions this zone covers. Different zones may cost different amounts.</td></tr>159 </table></div>160 <div class="crt">161 <div class="titlebar">Event tags - kind 30017</div>162<pre>{163 <span class="k">"tags"</span>: [[<span class="s">"d"</span>, <span class="c"><string, stall id></span>]]164}</pre>165 </div>166 <p>The <code>d</code> tag is required. Its value must equal the stall's <code>id</code> field. This is what makes the event addressable: a later 30017 event with the same <code>d</code> value replaces this one.</p>167168 <h2 class="bar" id="product"><span class="num">5</span> Product Event (kind 30018)</h2>169 <p>A product belongs to exactly one stall and carries its own price, quantity, and specs.</p>170 <div class="crt">171 <div class="titlebar">Event content - kind 30018</div>172<pre>{173 <span class="k">"id"</span>: <span class="c"><string, merchant-generated product id. Sequential ids discouraged></span>174 <span class="k">"stall_id"</span>: <span class="c"><string, id of the parent stall></span>175 <span class="k">"name"</span>: <span class="c"><string, product name></span>176 <span class="k">"description"</span>: <span class="c"><string, optional, product description></span>177 <span class="k">"images"</span>: <span class="c"><array of image URLs, optional></span>178 <span class="k">"currency"</span>: <span class="c"><string, currency used></span>179 <span class="k">"price"</span>: <span class="c"><float, cost of one unit></span>180 <span class="k">"quantity"</span>: <span class="c"><int or null, units available></span>181 <span class="k">"specs"</span>: [182 [<span class="c"><string, spec key></span>, <span class="c"><string, spec value></span>]183 ]184 <span class="k">"shipping"</span>: [185 {186 <span class="k">"id"</span>: <span class="c"><string, must match a shipping zone id from the stall></span>187 <span class="k">"cost"</span>: <span class="c"><float, extra shipping cost for this product></span>188 }189 ]190}</pre>191 </div>192 <div class="tscroll"><table class="ref">193 <caption>Product content fields</caption>194 <tr><th>Field</th><th>Type</th><th>Rule</th></tr>195 <tr><td class="btn">id</td><td>string</td><td>Merchant-chosen. Sequential ids are discouraged.</td></tr>196 <tr><td class="btn">stall_id</td><td>string</td><td>Must equal the <code>id</code> of an existing stall event by the same merchant.</td></tr>197 <tr><td class="btn">images</td><td>array of strings</td><td>Optional. Image URLs.</td></tr>198 <tr><td class="btn">price</td><td>float</td><td>Cost of one unit, in the stall's currency.</td></tr>199 <tr><td class="btn">quantity</td><td>int or null</td><td>Units in stock. Use <code>null</code> for unlimited items, such as digital goods or services.</td></tr>200 <tr><td class="btn">specs</td><td>array of [key, value] pairs</td><td>Optional. Structured specifications so a marketplace client can display and compare products, e.g. <code>[["operating_system", "Android 12.0"], ["screen_size", "6.4 inches"]]</code>.</td></tr>201 <tr><td class="btn">shipping</td><td>array of zone-cost objects</td><td>Optional. Extra shipping cost on top of the stall's base cost, for products that cost more to ship.</td></tr>202 <tr><td class="btn">shipping[].id</td><td>string</td><td>Must match one of the stall's shipping zone ids.</td></tr>203 <tr><td class="btn">shipping[].cost</td><td>float</td><td>Extra cost, added per unit ordered.</td></tr>204 </table></div>205 <p>To total the shipping cost for an order, add two numbers: the stall's base cost for the zone the customer chose, plus the product's per-unit shipping cost from this table, multiplied by the units ordered.</p>206 <div class="crt">207 <div class="titlebar">Event tags - kind 30018</div>208<pre>{209 <span class="k">"tags"</span>: [210 [<span class="s">"d"</span>, <span class="c"><string, product id></span>],211 [<span class="s">"t"</span>, <span class="c"><string, optional, product category></span>],212 [<span class="s">"t"</span>, <span class="c"><string, optional, another product category></span>]213 ]214}</pre>215 </div>216 <p>The <code>d</code> tag is required. Its value must equal the product's <code>id</code> field. The <code>t</code> tag is a searchable category, such as <code>food</code> or <code>fruits</code>. A product may carry several <code>t</code> tags.</p>217218 <div class="box tech">219 <span class="tag">Tech Note</span>220 <p>The source spec leaves two questions open and never resolves them. First, it asks whether <code>specs</code> should move into the tags array instead of staying in <code>content</code>, so relays could index it. Second, the customer order message (§6) has a <code>name</code> field marked only "???" in the source, with no stated purpose. Treat both as unresolved; do not assume behavior beyond what is written here.</p>221 </div>222223 <h2 class="bar" id="checkout"><span class="num">6</span> Checkout Messages</h2>224 <p>Checkout runs as a private conversation. The merchant and customer exchange kind-4 events, encrypted with <a href="04.html">NIP-04</a>. Every checkout message is a JSON object placed in the encrypted <code>content</code> field. Every message carries a <code>type</code> field that says what it represents.</p>225 <div class="tscroll"><table class="ref">226 <caption>Checkout message types</caption>227 <tr><th>Type</th><th>Sent by</th><th>Meaning</th></tr>228 <tr><td class="btn">0</td><td>Customer</td><td>New order.</td></tr>229 <tr><td class="btn">1</td><td>Merchant</td><td>Payment request.</td></tr>230 <tr><td class="btn">2</td><td>Merchant</td><td>Order status update.</td></tr>231 </table></div>232233 <h3>Type 0 - Customer Order</h3>234 <p>The customer sends this first, listing chosen products, quantities, and a shipping zone.</p>235 <div class="crt">236 <div class="titlebar">Checkout message - type 0, encrypted content</div>237<pre>{238 <span class="k">"id"</span>: <span class="c"><string, customer-generated order id></span>239 <span class="k">"type"</span>: 0240 <span class="k">"name"</span>: <span class="c"><string, optional></span>241 <span class="k">"address"</span>: <span class="c"><string, optional, required in practice for physical goods></span>242 <span class="k">"message"</span>: <span class="c"><string, optional, note for the merchant></span>243 <span class="k">"contact"</span>: {244 <span class="k">"nostr"</span>: <span class="c"><32-byte hex pubkey></span>245 <span class="k">"phone"</span>: <span class="c"><string, optional></span>246 <span class="k">"email"</span>: <span class="c"><string, optional></span>247 }248 <span class="k">"items"</span>: [249 { <span class="k">"product_id"</span>: <span class="c"><string></span>, <span class="k">"quantity"</span>: <span class="c"><int></span> }250 ]251 <span class="k">"shipping_id"</span>: <span class="c"><string, id of the chosen shipping zone></span>252}</pre>253 </div>254255 <h3>Type 1 - Merchant Payment Request</h3>256 <p>The merchant replies with one or more ways to pay. The customer picks one and pays outside Nostr.</p>257 <div class="crt">258 <div class="titlebar">Checkout message - type 1, encrypted content</div>259<pre>{260 <span class="k">"id"</span>: <span class="c"><string, order id this replies to></span>261 <span class="k">"type"</span>: 1262 <span class="k">"message"</span>: <span class="c"><string, optional></span>263 <span class="k">"payment_options"</span>: [264 { <span class="k">"type"</span>: <span class="c"><string, option type></span>, <span class="k">"link"</span>: <span class="c"><string, url, address, or invoice></span> }265 ]266}</pre>267 </div>268 <div class="tscroll"><table class="ref">269 <caption>Payment option types</caption>270 <tr><th>Type</th><th>Meaning</th></tr>271 <tr><td class="btn">url</td><td>Link to a payment page: Stripe, PayPal, BTCPay Server, and similar.</td></tr>272 <tr><td class="btn">btc</td><td>An on-chain bitcoin address.</td></tr>273 <tr><td class="btn">ln</td><td>A bitcoin Lightning invoice.</td></tr>274 <tr><td class="btn">lnurl</td><td>A bitcoin lnurl-pay string.</td></tr>275 </table></div>276 <p>Any payment method the merchant can verify is valid. The spec does not limit this list to the four types above.</p>277278 <h3>Type 2 - Merchant Status Update</h3>279 <p>The merchant sends this once payment is confirmed, and again when the order ships.</p>280 <div class="crt">281 <div class="titlebar">Checkout message - type 2, encrypted content</div>282<pre>{283 <span class="k">"id"</span>: <span class="c"><string, order id this replies to></span>284 <span class="k">"type"</span>: 2285 <span class="k">"message"</span>: <span class="c"><string, note for the customer></span>286 <span class="k">"paid"</span>: <span class="c"><bool, payment received></span>287 <span class="k">"shipped"</span>: <span class="c"><bool, order shipped></span>288}</pre>289 </div>290291 <figure class="diagram">292 <div class="frame">293 <svg viewBox="0 0 560 320" role="img" aria-label="Sequence diagram of the checkout flow between customer and merchant using encrypted kind 4 messages">294 <line class="d-wire" x1="110" y1="40" x2="110" y2="300"/>295 <line class="d-wire" x1="450" y1="40" x2="450" y2="300"/>296 <rect class="d-actor" x="45" y="8" width="130" height="30" rx="6"/>297 <text class="d-actor-label" x="110" y="28" text-anchor="middle">Customer</text>298 <rect class="d-actor" x="385" y="8" width="130" height="30" rx="6"/>299 <text class="d-actor-label" x="450" y="28" text-anchor="middle">Merchant</text>300 <path class="d-msg" d="M110,68 L445,68"/>301 <text class="d-label" x="280" y="61" text-anchor="middle">kind 4 - type 0 - New Order</text>302 <path class="d-msg blue" d="M450,105 L115,105"/>303 <text class="d-label" x="280" y="98" text-anchor="middle">kind 4 - type 1 - Payment Request</text>304 <text class="d-note" x="280" y="135" text-anchor="middle">Customer pays via chosen option - btc, ln, lnurl, or url - off relay</text>305 <path class="d-msg blue d-flow" d="M450,175 L115,175"/>306 <text class="d-label" x="280" y="168" text-anchor="middle">kind 4 - type 2 - Status: paid=true, shipped=false</text>307 <path class="d-msg blue d-flow" d="M450,215 L115,215"/>308 <text class="d-label" x="280" y="208" text-anchor="middle">kind 4 - type 2 - Status: paid=true, shipped=true</text>309 <text class="d-note" x="280" y="255" text-anchor="middle">All four messages are NIP-04 encrypted kind-4 events between the two pubkeys.</text>310 </svg>311 </div>312 <figcaption>Fig. 15-2 — <b>Checkout flow.</b> Customer and merchant trade four encrypted messages: order, payment request, then two status updates.</figcaption>313 </figure>314315 <div class="box tech">316 <span class="tag">Tech Note</span>317 <p>Checkout messages are defined only as <a href="04.html">NIP-04</a> encrypted content. NIP-04 is itself deprecated in favor of <a href="17.html">NIP-17</a>. The source spec was never updated to reflect this, so a compliant NIP-15 checkout still leaks sender, receiver, and timing metadata to every relay involved, on top of NIP-15's own complexity problems.</p>318 </div>319320 <h2 class="bar" id="marketplace-ui"><span class="num">7</span> Marketplace Customization (kind 30019)</h2>321 <p>A market operator can group several merchants under one branded storefront, and share it as a single <a href="19.html">NIP-19</a> <code>naddr</code> string. The <code>naddr</code> carries the event coordinates plus extra metadata, such as recommended relays.</p>322 <div class="crt">323 <div class="titlebar">Event content - kind 30019</div>324<pre>{325 <span class="k">"name"</span>: <span class="c"><string, optional, market name></span>326 <span class="k">"about"</span>: <span class="c"><string, optional, market description></span>327 <span class="k">"ui"</span>: {328 <span class="k">"picture"</span>: <span class="c"><string, optional, logo image URL></span>329 <span class="k">"banner"</span>: <span class="c"><string, optional, banner image URL></span>330 <span class="k">"theme"</span>: <span class="c"><string, optional, theme name></span>331 <span class="k">"darkMode"</span>: <span class="c"><bool></span>332 }333 <span class="k">"merchants"</span>: [<span class="c"><array of pubkeys, optional></span>]334}</pre>335 </div>336 <p>A marketplace client reads this event to theme itself: market name, description, logo, banner, and which merchant pubkeys to list.</p>337338 <h2 class="bar" id="auctions"><span class="num">8</span> Auctions</h2>339 <p>NIP-15 also supports selling a product at auction instead of a fixed price. An auction listing looks like a product, with bidding fields instead of a price.</p>340341 <h3>Event 30020 - Auction Product</h3>342 <div class="crt">343 <div class="titlebar">Event content - kind 30020</div>344<pre>{345 <span class="k">"id"</span>: <span class="c"><string, UUID, merchant-generated. Sequential ids discouraged></span>346 <span class="k">"stall_id"</span>: <span class="c"><string, UUID of the parent stall></span>347 <span class="k">"name"</span>: <span class="c"><string, product name></span>348 <span class="k">"description"</span>: <span class="c"><string, optional></span>349 <span class="k">"images"</span>: <span class="c"><array of image URLs, optional></span>350 <span class="k">"starting_bid"</span>: <span class="c"><int></span>351 <span class="k">"start_date"</span>: <span class="c"><int, optional, unix timestamp the auction starts></span>352 <span class="k">"duration"</span>: <span class="c"><int, seconds the auction runs, before extensions></span>353 <span class="k">"specs"</span>: [[<span class="c"><string, spec key></span>, <span class="c"><string, spec value></span>]]354 <span class="k">"shipping"</span>: [355 { <span class="k">"id"</span>: <span class="c"><string, UUID, must match a stall shipping zone></span>, <span class="k">"cost"</span>: <span class="c"><float></span> }356 ]357}</pre>358 </div>359 <p>Fields work the same as a fixed-price product, with two additions. <code>start_date</code> may be left out when the start date is unknown or hidden; the merchant edits the event later to set it. <code>duration</code> counts seconds from <code>start_date</code>, not counting any later extension.</p>360 <div class="box warn">361 <span class="tag">Warning!</span>362 <p>An auction is an addressable event, so the merchant can edit it freely before the first bid arrives. After the first bid, do not edit it again. A bid references the auction's event <code>id</code>, not its stall or product UUID. Editing the auction creates a new event id, and every bid made against the old id becomes orphaned.</p>363 </div>364365 <h3>Event 1021 - Bid</h3>366 <div class="crt">367 <div class="titlebar">Event - kind 1021</div>368<pre>{369 <span class="k">"content"</span>: <span class="c"><int, bid amount, in sats></span>370 <span class="k">"tags"</span>: [[<span class="s">"e"</span>, <span class="c"><event id of the kind-30020 auction></span>]]371}</pre>372 </div>373 <p>A bid is a regular event. Its <code>content</code> is the bid amount as a plain integer, in the currency of the auction. Every bid must reference exactly one auction with an <code>e</code> tag.</p>374375 <h3>Event 1022 - Bid Confirmation</h3>376 <p>A bid only counts once the merchant confirms it. A marketplace client should treat unconfirmed bids as not yet valid.</p>377 <div class="crt">378 <div class="titlebar">Event content and tags - kind 1022</div>379<pre>{380 <span class="k">"content"</span>: {381 <span class="k">"status"</span>: <span class="c"><string, one of "accepted", "rejected", "pending", "winner"></span>382 <span class="k">"message"</span>: <span class="c"><string, optional></span>383 <span class="k">"duration_extended"</span>: <span class="c"><int, optional, seconds added to the auction></span>384 }385 <span class="k">"tags"</span>: [386 [<span class="s">"e"</span>, <span class="c"><event id of the bid being confirmed></span>],387 [<span class="s">"e"</span>, <span class="c"><event id of the auction></span>]388 ]389}</pre>390 </div>391 <div class="tscroll"><table class="ref">392 <caption>Bid confirmation status values</caption>393 <tr><th>Status</th><th>Meaning</th></tr>394 <tr><td class="btn">accepted</td><td>The bid is valid and stands.</td></tr>395 <tr><td class="btn">pending</td><td>Not yet approved. May still be approved after more steps, such as extra verification of the bidder.</td></tr>396 <tr><td class="btn">rejected</td><td>Refused. Cannot later be approved. The optional <code>message</code> field may explain why.</td></tr>397 <tr><td class="btn">winner</td><td>Sent to the winning bid once the auction ends and the merchant picks a winner.</td></tr>398 </table></div>399 <p>A marketplace client should subscribe to kind 1022 for every auction it follows, alongside the kind 1021 bids. It must also check that the confirmation's pubkey matches the merchant's pubkey, in addition to checking the event signature.</p>400 <p>A merchant may extend an auction close to its end date by sending a confirmation with <code>duration_extended</code>. The real end time is always <code>start_date + duration</code>, plus the sum of every <code>duration_extended</code> value sent across all confirmations for that auction.</p>401402 <figure class="diagram">403 <div class="frame">404 <svg viewBox="0 0 560 190" role="img" aria-label="Bidder publishes a kind 1021 bid; the merchant publishes a kind 1022 confirmation; both propagate through relays to watching clients">405 <rect class="d-actor" x="10" y="70" width="110" height="50" rx="8"/>406 <text class="d-actor-label" x="65" y="99" text-anchor="middle">Bidder</text>407 <rect class="d-actor" x="225" y="70" width="110" height="50" rx="8"/>408 <text class="d-actor-label" x="280" y="99" text-anchor="middle">Relay</text>409 <rect class="d-actor" x="440" y="30" width="110" height="44" rx="8"/>410 <text class="d-actor-label" x="495" y="57" text-anchor="middle">Merchant</text>411 <rect class="d-actor" x="440" y="116" width="110" height="44" rx="8"/>412 <text class="d-actor-label" x="495" y="143" text-anchor="middle">Watchers</text>413 <path class="d-msg d-flow" d="M122,90 C165,84 185,80 223,78"/>414 <text class="d-label" x="175" y="62" text-anchor="middle">1021 bid</text>415 <path class="d-msg blue d-flow" d="M337,80 C380,68 400,58 438,52"/>416 <path class="d-msg d-flow" d="M495,74 C480,90 480,100 495,114"/>417 <text class="d-label" x="495" y="98" text-anchor="middle">1022 confirm</text>418 <path class="d-msg blue d-flow" d="M337,100 C380,112 400,120 438,132"/>419 </svg>420 </div>421 <figcaption>Fig. 15-3 — <b>Auction bidding.</b> Bids and confirmations are both regular events that flow through the same relays as any other Nostr event.</figcaption>422 </figure>423424 <h2 class="bar"><span class="num">9</span> Customer Support</h2>425 <p>Support conversations after a sale use whatever channel the merchant listed. If that channel is Nostr, use <a href="04.html">NIP-04</a> encrypted messages, the same as checkout.</p>426427 <h2 class="bar" id="examples"><span class="num">10</span> Worked Example</h2>428 <p>A merchant lists a stall and a product, then a customer buys it. Orange lines are published by the sender shown in the comment. Checkout content is shown decrypted for clarity; on the wire it is NIP-04 ciphertext.</p>429 <div class="crt">430 <div class="titlebar">Session transcript - wss://relay.example.com</div>431<pre><span class="c">-- Merchant publishes a stall --</span>432<span class="send">→ ["EVENT", {"id":"a11f02…","pubkey":"merch0f7…","kind":30017,433 "tags":[["d","stall-fruit-01"]],434 "content":"{\"id\":\"stall-fruit-01\",\"name\":\"Fresh Fruit Stand\",435 \"currency\":\"sat\",436 \"shipping\":[{\"id\":\"zone-eu\",\"name\":\"Europe\",\"cost\":1500,437 \"regions\":[\"EU\"]}]}",438 "sig":"9a2c1e…"}]</span>439<span class="recv">← ["OK", "a11f02…", true, ""]</span>440441<span class="c">-- Merchant publishes a product in that stall --</span>442<span class="send">→ ["EVENT", {"id":"b220d4…","pubkey":"merch0f7…","kind":30018,443 "tags":[["d","prod-apples-01"],["t","food"],["t","fruits"]],444 "content":"{\"id\":\"prod-apples-01\",\"stall_id\":\"stall-fruit-01\",445 \"name\":\"Apples, 1kg\",\"currency\":\"sat\",\"price\":900,446 \"quantity\":40,\"shipping\":[{\"id\":\"zone-eu\",\"cost\":0}]}",447 "sig":"7fd310…"}]</span>448<span class="recv">← ["OK", "b220d4…", true, ""]</span>449450<span class="c">-- Customer orders 2kg of apples, via a NIP-04 kind-4 DM to the merchant --</span>451<span class="c">-- plaintext content before encryption: --</span>452<span class="c">{"id":"ord-77x","type":0,"address":"12 Market Row",453 "contact":{"nostr":"cust99a…"},454 "items":[{"product_id":"prod-apples-01","quantity":2}],455 "shipping_id":"zone-eu"}</span>456<span class="send">→ ["EVENT", {"id":"c33ab1…","pubkey":"cust99a…","kind":4,457 "tags":[["p","merch0f7…"]],"content":"Bq7f==?iv=Nn3xR7==","sig":"1e88ac…"}]</span>458<span class="recv">← ["OK", "c33ab1…", true, ""]</span>459460<span class="c">-- Merchant replies with a payment request --</span>461<span class="c">-- plaintext: {"id":"ord-77x","type":1,"payment_options":</span>462<span class="c">-- [{"type":"ln","link":"lnbc18u1p3xyz…"}]}</span>463<span class="send">→ ["EVENT", {"id":"d4412c…","pubkey":"merch0f7…","kind":4,464 "tags":[["p","cust99a…"],["e","c33ab1…"]],465 "content":"u3f9skA2==?iv=q8x2Lp0zK1==","sig":"3d8e2f…"}]</span>466<span class="recv">← ["OK", "d4412c…", true, ""]</span>467468<span class="c">-- Merchant confirms payment, then later confirms shipping --</span>469<span class="c">-- plaintext: {"id":"ord-77x","type":2,"paid":true,"shipped":false}</span>470<span class="send">→ ["EVENT", {"id":"e551fd…","pubkey":"merch0f7…","kind":4,471 "tags":[["p","cust99a…"],["e","c33ab1…"]],472 "content":"z9y1Nn3xR7==?iv=A2q8x2Lp==","sig":"6b0a4d…"}]</span>473<span class="recv">← ["OK", "e551fd…", true, ""]</span>474<span class="c">-- plaintext: {"id":"ord-77x","type":2,"paid":true,"shipped":true}</span>475<span class="send">→ ["EVENT", {"id":"f66a0e…","pubkey":"merch0f7…","kind":4,476 "tags":[["p","cust99a…"],["e","c33ab1…"]],477 "content":"Lp0zK1u3f9sk==?iv=x2R7z9y1==","sig":"a19c7e…"}]</span>478<span class="recv">← ["OK", "f66a0e…", true, ""]</span></pre>479 </div>480481 <h2 class="bar" id="history"><span class="num">11</span> Version History</h2>482 <div class="box tech">483 <span class="tag">Tech Note</span>484 <p>The number 15 was used twice. The first NIP-15, "End of Stored Events Notice" by Semisol, was drafted 2022-05-26 and merged into <a href="01.html">NIP-01</a> on 2023-04-09 (that merge is the origin of the <code>EOSE</code> message defined in NIP-01 §5). Four days later, on 2023-04-13, the freed number 15 was reused for this unrelated Nostr Marketplace spec. The two documents share no content. The history below covers only the Marketplace spec.</p>485 </div>486 <p>Notable changes to the Marketplace spec, newest first:</p>487 <ul class="patchnotes">488 <li class="minor"><span class="ver">2026</span><span class="when">2026-07-26</span>Reworded the deprecation notice to name <a href="99.html">NIP-99</a> explicitly.</li>489 <li><span class="ver">2026</span><span class="when">2026-05-31</span>Marked the spec <code>unrecommended</code> and added the deprecation warning pointing to NIP-99.</li>490 <li class="minor"><span class="ver">2024</span><span class="when">2024-08-20</span>Renamed "parameterized replaceable event" to "addressable event" throughout, matching the NIP-01 terminology change.</li>491 <li><span class="ver">2024</span><span class="when">2024-01-09</span>Added the entire Auctions section: event 30020, kind 1021 bids, and kind 1022 bid confirmations (PR #859).</li>492 <li><span class="ver">2023</span><span class="when">2023-10-31</span>Clarified that <code>quantity: null</code> means unlimited availability, for digital items and services.</li>493 <li><span class="ver">2023</span><span class="when">2023-09-08</span>Added the <code>description</code> field to the kind 30019 marketplace customization event (PR #768).</li>494 <li><span class="ver">2023</span><span class="when">2023-08-15</span>Renamed shipping zone "countries" to "regions" (PR #465).</li>495 <li><span class="ver">2023</span><span class="when">2023-08-10</span>Added optional per-product shipping cost, on top of the stall's base cost (ktecho).</li>496 <li class="minor"><span class="ver">2023</span><span class="when">2023-11-15</span>Repo-wide cleanup removed the per-file author badges from the spec text.</li>497 <li><span class="ver">Origin</span><span class="when">2023-04-13</span>Arc (benarc), with Andrew Camilleri and Vlad Stan, published Nostr Marketplace, based on the lnbits Diagon-Alley plugin (PR #330), reusing the number freed four days earlier.</li>498 </ul>499500 <div class="credits">501 <h3 class="cardhead">Original Proposal - Credits</h3>502 <table>503 <tr><td>Author</td><td>Arc (benarc), based on Diagon-Alley by lnbits</td></tr>504 <tr><td>First commit</td><td>2023-04-13 — “NIP-15 Nostr marketplace (#330)”</td></tr>505 <tr><td>Status</td><td><span class="chip draft">draft</span> <span class="chip unrecommended">unrecommended</span> — replaced by <a href="99.html">NIP-99</a></td></tr>506 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/15.md">15.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/15.md">full change history</a></td></tr>507 </table>508 </div>509510 <h2 class="bar"><span class="num">12</span> See Also</h2>511 <ul>512 <li><a href="99.html">NIP-99</a> — Classified Listings. The recommended replacement for this spec.</li>513 <li><a href="01.html">NIP-01</a> — addressable events, the storage class used by kinds 30017, 30018, 30019, and 30020.</li>514 <li><a href="04.html">NIP-04</a> — encrypted direct messages, used for all checkout and support communication. Itself deprecated.</li>515 <li><a href="17.html">NIP-17</a> — private direct messages, the recommended replacement for NIP-04.</li>516 <li><a href="09.html">NIP-09</a> — event deletion, used for kind 5 to remove a stall or product.</li>517 <li><a href="19.html">NIP-19</a> — bech32 encodings, source of the <code>naddr</code> used to share a customized marketplace.</li>518 </ul>519 </div>520521 <aside>522 <div class="cartridge">523 <div class="inner">524 <h3 class="cardhead">Spec Card</h3>525 <div class="lights"><span class="led err"></span><span class="led err"></span><span class="led err"></span></div>526 <dl>527 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip unrecommended">unrecommended</span><span class="chip">optional</span></dd>528 <dt>Defines</dt><dd>stall - product - checkout messages - auctions - bids</dd>529 <dt>Kinds</dt><dd>0 - 4 - 5 - 30017 - 30018 - 30019 - 30020 - 1021 - 1022</dd>530 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> - <a href="04.html">NIP-04</a> - <a href="09.html">NIP-09</a> - <a href="19.html">NIP-19</a></dd>531 <dt>Transport</dt><dd>relay events - NIP-04 encrypted DMs for checkout</dd>532 <dt>Replaced by</dt><dd><a href="99.html">NIP-99</a></dd>533 <dt>First commit</dt><dd>2023-04-13</dd>534 <dt>Last updated</dt><dd>2026-07-26</dd>535 </dl>536 </div>537 </div>538 </aside>539 </main>540541 <nav class="navbtns">542 <a href="14.html">◀ NIP-14: Subject tag in Text events</a>543 <a href="16.html">NIP-16: Event Treatment ▶</a>544 </nav>545546 <footer class="pagefoot">547 <a href="../index.html">Nostr Protocol Manual</a>548 <span class="pageno">NIP 15 / F4</span>549 </footer>550</div>551552<script src="../assets/webmcp.js" defer></script>553</body>554</html>