Step 0 - Make sure you can receive zaps
Before you embed anything, your Nostr profile must be “zap-ready”. If the zap button cannot find a Lightning address / LNURL in your profile metadata, it can’t generate an invoice.
lud16) or an LNURL (often stored as lud06) in your Nostr profile metadata (kind 0).
Most Nostr clients provide a field like “Lightning address” or “Zap address”.
Mental model (quick)
- Zap button → looks up your profile, finds your zap endpoint, requests an invoice, then lets the user pay (WebLN or QR).
- Viewer button → reads zap receipts from relays and shows them in a dialog.
Step 1 - Include the bundle
Add this near the end of your page (before </body>).
We recommend using the full URL so it works anywhere.
<script src="https://zap.nostr.buzz/dist/nostr-zap.js"></script>
Step 2 - Add a Zap button
Add a button with data-npub to target a profile.
Optionally provide data-relays and data-theme.
<button
data-npub="npub1..."
data-relays="wss://relay.damus.io,wss://relay.primal.net,wss://nos.lol"
data-theme="dark">
⚡️ Send a zap
</button>
Step 3 - Add a “View zaps” button
The library auto-wires any button[data-nzv-id] on click and opens a dialog.
Use it to show zaps for a profile, a note, or an addressable event.
<button
data-nzv-id="npub1..."
data-relay-urls="wss://relay.damus.io,wss://relay.primal.net,wss://nos.lol"
data-title=""
data-zap-color-mode="true">
👀 View zaps
</button>
🧷 Viewer attributes
-
data-nzv-id(required): Nostr identifier (npub, nprofile, note, nevent, naddr). -
data-relay-urls(required): comma-separated relay URLs. -
data-title(optional): dialog title. -
data-zap-color-mode(optional): "true" or "false". -
data-theme(optional): "light" or "dark".
Theming
You can force a theme per button:
data-theme="light" or data-theme="dark".
If you don’t set it, the dialogs follow prefers-color-scheme.
<button data-npub="npub1..." data-theme="light">🌞 Light zap</button>
<button data-npub="npub1..." data-theme="dark">🌙 Dark zap</button>
<button
data-nzv-id="npub1..."
data-relay-urls="wss://relay.damus.io,wss://relay.primal.net,wss://nos.lol"
data-theme="dark">
🌙 View zaps
</button>
JavaScript API
After you include the bundle, the library is available as window.nostrZap.
In most cases you don’t need to call anything manually.
-
window.nostrZap.initialize()- optional manual initialization -
window.nostrZap.nostrZapView()- open the zap viewer dialog
🧾 Legacy zap buttons
Older embeds remain compatible. Common attributes:
data-npub, data-note-id, and data-relays.
// Low-level API object:
// window.nostrZap.nostrZap.init({ npub, noteId?, naddr?, relays?, buttonColor?, anon? })
// Convenience helpers:
await window.nostrZap.zapInit({
npub: "npub1...",
relays: "wss://relay.damus.io,wss://nos.lol",
});
window.nostrZap.zapInitTargets();
🔒 Zapwall (paywalled content)
Zapwall lets you gate content per-item and unlock it after a successful zap. It supports two modes: blur mode (easy, not secret) and encrypted payload mode (no plaintext shipped in HTML).
1) Quick start (blur mode)
In blur mode, the content is present in HTML but visually gated until a zap succeeds. This is good for UX, but it is not “secret” (view-source can still show it).
<section
data-zapwall
data-zapwall-id="post-123"
data-title="Premium post"
data-amount="21"
data-npub="npub1..."
data-relays="wss://relay.damus.io,wss://relay.primal.net,wss://nos.lol">
<div data-zapwall-content>
<h2>Premium content</h2>
<p>This becomes visible after a successful zap.</p>
</div>
</section>
2) Secure mode (encrypted payload, no plaintext in HTML)
In encrypted mode, you do not ship the plaintext in the DOM. Instead you ship an encrypted payload and decrypt it only after: (a) the zap is successful, and (b) a decryption key is provided.
v: version (currently1)alg:AES-256-GCMiv: base64 IV (12+ bytes)ct: base64 ciphertextformat:htmlortextkeyHint(optional): a human-friendly hint shown in the key prompt
<section
data-zapwall
data-zapwall-id="post-locked-1"
data-title="Premium post"
data-amount="21"
data-npub="npub1..."
data-relays="wss://relay.damus.io,wss://relay.primal.net,wss://nos.lol"
data-zapwall-key-label="Enter your key">
<!-- empty container (plaintext is NOT present in HTML) -->
<div data-zapwall-content></div>
<!-- encrypted payload JSON -->
<script type="application/json" data-zapwall-payload>
{"v":1,"alg":"AES-256-GCM","iv":"...","ct":"...","format":"html","keyHint":"provided after payment"}
</script>
</section>
3) Providing the decryption key
For encrypted mode, the library needs a 32-byte key (AES-256). You can provide it in either: 64-hex or base64.
Recommended approach: implement a key provider function. The provider can check whatever you want (zap receipt, DM, etc.) and return the key.
// Option A: global provider
window.nostrZapZapwallKeyProvider = async ({ wall, zap }) => {
// wall.key / wall.amountSats / wall.noteId / wall.naddr / wall.npub ...
// zap.invoice / zap.amountSats / zap.preimage (wallet-dependent) ...
// Return 32-byte key as 64-hex or base64.
return null;
};
// Option B: API (if you import the module)
// window.nostrZap.setZapWallKeyProvider(async ({ wall, zap }) => "...key...");
4) Encrypting content offline
This repo includes an offline helper script to generate the encrypted payload. It outputs payload + a random key. You must deliver the key to paying users via a secure channel.
# Example (run locally)
node scripts/zapwall-encrypt.mjs ./premium.html --format html --key-hint "Delivered after payment"
# Output JSON contains:
# - payload: { v, alg, iv, ct, format, keyHint? }
# - key: 64-hex AES key (keep this secret!)
Relays (recommended)
Your buttons should use multiple relays. For best results, use a mix of reliable read+write relays.
data-relays (string).data-relay-urls (comma-separated list).Troubleshooting
- “Unable to fetch invoice”: your profile likely has no Lightning address / LNURL in metadata, or the endpoint is unreachable.
- No zaps show up: try different relays (zaps may not be replicated everywhere).
-
Theme not applied: set
data-themedirectly on the button that opens the dialog. - Payments: users with WebLN get a one-click flow; otherwise they can scan/copy the invoice QR.