Chapter 57 - Payments
57
Nostr Implementation Possibility - Hex № 57

Lightning Zaps

Record Lightning payments between users as signed events: a zap request asks a wallet for an invoice, and a zap receipt confirms the payment.

1 Overview

A zap is a Lightning payment that leaves a public trace on Nostr. This NIP defines two event kinds for that trace. Kind 9734 is a zap request. It represents a payer's request to a recipient's Lightning wallet for an invoice. Kind 9735 is a zap receipt. It represents the confirmation, by the recipient's Lightning wallet, that the invoice issued for a zap request has been paid.

The sender is the user who pays. The recipient is the user who gets paid. The recipient's wallet sits behind an LNURL server: an HTTP service that issues Lightning invoices per the lnurl specifications.

With zap receipts on Nostr, clients can display Lightning payments from entities on the network. These can be used for fun or for spam deterrence.

Warning!

A zap receipt is not a proof of payment. It only proves that some Nostr user fetched an invoice. The receipt implies the invoice was paid, but a rogue implementation could lie. You trust the author of the zap receipt for the legitimacy of the payment.

2 Protocol Flow

A zap runs through nine steps. HTTP carries the request and the invoice. Lightning carries the money. Nostr relays carry the receipt.

  1. The client finds the recipient's lnurl pay request URL. If the target event has a zap tag, calculate it from that tag (see §9). Otherwise decode the lud16 field on the recipient's profile per the lnurl specifications. The client MUST send a GET request to this URL and parse the response. If allowsNostr exists and is true, and if nostrPubkey exists and is a valid BIP-340 public key in hex, the client should associate this information with the user, along with the response's callback, minSendable, and maxSendable values.
  2. Clients may display a zap button on each post or on a user's profile. If the recipient's lnurl endpoint supports Nostr, the client SHOULD use this NIP to request a zap receipt instead of a normal lnurl invoice.
  3. When the sender starts a zap, the client creates a kind 9734 zap request event as described in §3 and signs it.
  4. The client does not publish the zap request. It sends the event to the callback URL from the lnurl pay endpoint, using a GET request. See §4.
  5. The recipient's LNURL server receives the zap request and validates it. See §5 for server configuration and §6 for the validation rules.
  6. If the zap request is valid, the server fetches a description hash invoice. The description is this zap request note, and this note only. No additional lnurl metadata goes into the description. The server returns the invoice per LUD06.
  7. On receiving the invoice, the client MAY pay it or pass it to an app that can pay it.
  8. Once the invoice is paid, the recipient's LNURL server MUST generate a zap receipt as described in §7. It publishes the receipt to the relays listed in the zap request.
  9. Clients MAY fetch zap receipts on posts and profiles, but MUST authorize their validity as described in §8. If the zap request has a non-empty content, the client may display it as a zap comment. Generally clients should show users the zap request note, and use the zap receipt to show "zap authorized by ...". This is optional.
Sender client LNURL server Relays GET /.well-known/lnurlp/<user> allowsNostr, nostrPubkey, callback GET callback?amount&nostr=9734&lnurl {"pr": bolt11 invoice} pay invoice (Lightning network) ["EVENT", kind 9735 receipt] Recipient and sender clients fetch the zap receipt from the relays.
Fig. 57-1 — The zap flow. The zap request travels over HTTP, the payment over Lightning, and the receipt over Nostr relays.

3 The Zap Request (kind 9734)

A zap request is an event of kind 9734. It is not published to relays. The client sends it to the recipient's lnurl pay callback URL instead. The content MAY be an optional message to send along with the payment.

The event MUST include these tags:

Required zap request tags
TagValueRule
relaysone or more relay URLsThe relays the recipient's wallet should publish its zap receipt to. Put each URL as its own element of the tag. Do not nest the relays in an additional list.
amountstring, millisatsThe amount the sender intends to pay, in millisats, formatted as a string. Recommended, but optional.
lnurlbech32 stringThe lnurl pay URL of the recipient, encoded with bech32 and the prefix lnurl. Recommended, but optional.
phex pubkeyThe hex-encoded pubkey of the recipient.

The event MAY include these tags:

Optional zap request tags
TagValueRule
ehex event idOptional. Clients MUST include it when they zap an event rather than a person.
aevent coordinateOptional. Allows tipping addressable events, such as NIP-23 long-form notes.
kstringThe stringified kind of the target event.
Example - zap request (kind 9734)
{
  "kind": 9734,
  "content": "Zap!",
  "tags": [
    ["relays", "wss://nostr-pub.wellorder.com", "wss://anotherrelay.example.com"],
    ["amount", "21000"],
    ["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"],
    ["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"],
    ["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"],
    ["k", "1"]
  ],
  "pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
  "created_at": 1679673265,
  "id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93",
  "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d"
}

4 Sending the Request over HTTP

The client sends the signed zap request with an HTTP GET request to the recipient's callback URL. The lnurl pay endpoint provided that URL in step 1. The request should define these query parameters:

Callback query parameters
ParameterContent
amountThe amount in millisats the sender intends to pay.
nostrThe kind 9734 zap request event, JSON encoded then URI encoded.
lnurlThe lnurl pay URL of the recipient, encoded with bech32 and the prefix lnurl.

The response is a JSON object with a pr key. The pr value is the invoice the sender must pay to finalize the zap. This flow looks like this in JavaScript:

Example - client-side flow (JavaScript)
// senderPubkey: the sender's pubkey
// recipientPubkey: the recipient's pubkey
// callback: from the recipient's lnurl pay endpoint
// lnurl: the recipient's lightning address, encoded as a lnurl
const sats = 21

const amount = sats * 1000
const relays = ['wss://nostr-pub.wellorder.net']
const event = encodeURI(JSON.stringify(await signEvent({
  kind: 9734,
  content: "",
  pubkey: senderPubkey,
  created_at: Math.round(Date.now() / 1000),
  tags: [
    ["relays", ...relays],
    ["amount", amount.toString()],
    ["lnurl", lnurl],
    ["p", recipientPubkey],
  ],
})))

const {pr: invoice} =
  await fetchJson(`${callback}?amount=${amount}&nostr=${event}&lnurl=${lnurl}`)

5 LNURL Server Configuration

An LNURL server needs two extra fields so that clients know it supports zap invoices. Add them to the lnurl-pay static endpoint /.well-known/lnurlp/<user>:

  1. Add a nostrPubkey field. This is the Nostr pubkey your server uses to sign zap receipt events. Clients use it to validate zap receipts.
  2. Add an allowsNostr field and set it to true.
Hint!

The nostrPubkey belongs to the server, not to the user. One zap-enabled server signs every receipt it publishes with this one key. Clients compare a receipt's pubkey against it in §8.

6 Server-Side Validation

When a client calls the lnurl-pay callback URL, the nostr query parameter holds the zap request event, URI- and JSON-encoded. If the parameter is present, the server must validate the event with these rules:

  1. It MUST have a valid Nostr signature.
  2. It MUST have tags.
  3. It MUST have only one p tag.
  4. It MUST have 0 or 1 e tags.
  5. There should be a relays tag with the relays to send the zap receipt to.
  6. If there is an amount tag, it MUST equal the amount query parameter.
  7. If there is an a tag, it MUST be a valid event coordinate.
  8. There MUST be 0 or 1 P tags. If there is one, it MUST equal the zap receipt's pubkey.

The server MUST then store the event for later use, when the invoice is paid.

Tech Note

Rule 8 is inconsistent with §7. Here, a P tag in the zap request must equal the zap receipt's pubkey — the LNURL server's nostrPubkey. In §7, the P tag of the zap receipt holds the zap sender's pubkey instead. The source spec defines no sender-side meaning for a P tag in a zap request. Implementations should treat rule 8 with care.

7 The Zap Receipt (kind 9735)

A zap receipt is an event of kind 9735. A Lightning node creates it when an invoice generated by a zap request is paid. Zap receipts are only created when the invoice description — committed to the description hash — contains a zap request note.

When the payment arrives, the server executes these steps:

  1. Get the description for the invoice. Save it somewhere during the generation of the description hash invoice. CLN, the reference implementation here, saves it automatically.
  2. Parse the bolt11 description as a JSON Nostr event. This SHOULD be validated against the rules in §6, either when it is received, or before the invoice is paid.
  3. Create a Nostr event of kind 9735 as described below. Publish it to the relays declared in the zap request.

The following should be true of the zap receipt event:

  • The content SHOULD be empty.
  • The created_at date SHOULD be set to the invoice paid_at date, for idempotency.
  • tags MUST include the p tag (zap recipient) AND the optional e tag from the zap request AND the optional a tag from the zap request AND the optional P tag from the pubkey of the zap request (zap sender).
  • The receipt MUST have a bolt11 tag that contains the description hash bolt11 invoice.
  • The receipt MUST contain a description tag: the JSON-encoded zap request.
  • SHA256(description) SHOULD match the description hash in the bolt11 invoice.
  • The receipt MAY contain a preimage tag to match against the payment hash of the bolt11 invoice. This is not really a payment proof. There is no real way to prove that the invoice is real or has been paid. You trust the author of the zap receipt for the legitimacy of the payment.

A reference implementation for a zap-enabled LNURL server is cln-nostr-zapper.

Zap request kind 9734 JSON sha256 description hash bolt11 invoice → bolt11 tag Receipt kind 9735 → description tag commit copy verbatim
Fig. 57-2 — The description hash link. The invoice commits to the sha256 of the zap request. The receipt carries both the invoice and the request.
Example - zap receipt (kind 9735)
{
  "id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446",
  "pubkey": "9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31",
  "created_at": 1674164545,
  "kind": 9735,
  "tags": [
    ["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
    ["P", "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"],
    ["e", "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],
    ["k", "1"],
    ["bolt11", "lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0"],
    ["description", "{\"pubkey\":\"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322\",\"content\":\"\",\"id\":\"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d\",\"created_at\":1674164539,\"sig\":\"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d\",\"kind\":9734,\"tags\":[[\"e\",\"3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"relays\",\"wss://relay.damus.io\",\"wss://nostr-relay.wlvs.space\",\"wss://nostr.fmt.wiz.biz\",\"wss://relay.nostr.bg\",\"wss://nostr.oxtr.dev\",\"wss://nostr.v0l.io\",\"wss://brb.io\",\"wss://nostr.bitcoiner.social\",\"ws://monad.jb55.com:8080\",\"wss://relay.snort.social\"]]}"],
    ["preimage", "5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f"]
  ],
  "content": ""
}
Tech Note

The source spec's receipt example is not valid JSON. It has a trailing comma after content and omits the sig field. A real kind 9735 event must carry a valid sig per NIP-01. The example above fixes the comma but keeps the source's field set. The source also mentions the lud16 profile field only in the flow; older lud06 lookup was removed from the spec in 2025.

8 Client-Side Receipt Validation

A client can retrieve zap receipts on events and pubkeys with a NIP-01 filter, for example {"kinds": [9735], "#e": [...]}. Zaps MUST be validated with these steps:

  • The zap receipt's pubkey MUST be the same as the recipient's lnurl provider's nostrPubkey, retrieved in step 1 of the protocol flow.
  • The invoiceAmount in the receipt's bolt11 tag MUST equal the amount tag of the zap request, if present.
  • The lnurl tag of the zap request, if present, SHOULD equal the recipient's lnurl.

9 Zap Splits: the zap Tag

An event can carry one or more zap tags. Clients that want to zap such an event SHOULD calculate the lnurl pay request from these tags, not from the author's profile field. Each zap tag has this shape: the second element is the hex pubkey of a receiver. The third element is the relay to download the receiver's kind-0 metadata from. The optional fourth element is a weight: a generalization of a percentage.

Clients should parse all weights, calculate their sum, and then a percentage for each receiver. If no weights are present, clients should divide the zap amount equally across all receivers. If weights are only partially present, receivers without a weight get weight = 0 and are not zapped.

Example - zap split tags on an event
{
  "tags": [
    ["zap", "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", "wss://nostr.oxtr.dev", "1"],  // 25%
    ["zap", "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52", "wss://nostr.wine/",    "1"],  // 25%
    ["zap", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", "wss://nos.lol/",       "2"]   // 50%
  ]
}

Clients MAY display the zap split configuration in the note.

Zap amount 21000 msat weight 1 → 25% - 5250 msat weight 1 → 25% - 5250 msat weight 2 → 50% - 10500 msat
Fig. 57-3 — Zap split by weight. Weights 1, 1, and 2 sum to 4. Each receiver gets its weight divided by the sum.

10 Worked Example

Alice zaps 21 sats to Bob's note. Her client already fetched Bob's lnurl endpoint and saw allowsNostr: true. The transcript shows the HTTP leg first, then the relay leg. Orange lines go out from a client. Blue lines come back.

Session transcript - HTTP callback + wss://relay.damus.io
-- 1. Alice's client asks Bob's LNURL server for an invoice --
→ GET https://pay.bob.example/callback?amount=21000
     &nostr=%7B%22kind%22%3A9734%2C%22content%22%3A%22Zap!%22%2C...%7D
     &lnurl=lnurl1dp68gurn8ghj7...
← {"pr": "lnbc210n1p3unwfu...qy9qxpq"}

-- 2. Alice pays the bolt11 invoice with her Lightning wallet --

-- 3. Bob's LNURL server publishes the receipt to the request's relays --
→ ["EVENT", {"kind":9735, "pubkey":"9630f464cca6…",
     "created_at":1674164545,  // = invoice paid_at
     "tags":[["p","32e18276…"], ["P","97c70a44…"], ["e","3624762a…"],
             ["bolt11","lnbc210n1p3unwfu…"], ["description","{\"kind\":9734,…}"],
             ["preimage","5d006d2c…"]],
     "content":"", "sig":"c2e9f3a1…"}]
← ["OK", "67b48a14fb…", true, ""]

-- 4. Carol's client fetches and validates zaps on Bob's note --
→ ["REQ", "zaps", {"kinds":[9735], "#e":["3624762a1274dd96…"]}]
← ["EVENT", "zaps", {"kind":9735, "pubkey":"9630f464cca6…", …}]
← ["EOSE", "zaps"]
-- Carol checks: pubkey == Bob's nostrPubkey; bolt11 amount == 21000 msat --
Hint!

Zaps can become more private in the future. Encrypt the zap request note to the target user, and only the recipient can read who paid. The spec leaves this out of the initial draft for simplicity.

11 Version History

William Casarin proposed zaps in February 2023. Damus and Snort shipped them within weeks. Notable spec changes, newest first:

  • 20252025-09Removed the lud06 profile field from the lookup flow. Clients now decode lud16 only.
  • 20252025-02Relaxed the description hash check: SHA256(description) SHOULD (no longer MUST) match the bolt11 invoice.
  • 20242024-01Added the P tag so zap receipts index the zap sender.
  • 20232023-09Zap receipts must copy the optional a tag from the zap request.
  • 20232023-05Added zap splits with weighted zap tags. Renamed "zap note" to "zap receipt".
  • 20232023-03Full rewrite: consolidated the protocol flow and the appendix structure, clarified terminology.
  • 20232023-02Added the amount tag and the a tag for addressable event coordinates.
  • Origin2023-02-13William Casarin published NIP-57 (#224).

Original Proposal - Credits

AuthorWilliam Casarin (jb55)
First commit2023-02-13 — “NIP-57: Lightning Zaps (#224)”
Pull requestnostr-protocol/nips#224
Statusdraft - optional — clients and LNURL servers may implement it
Source57.md on GitHub - full change history

12 See Also

  • NIP-01 — event format, filters, and the relay messages used to fetch receipts.
  • NIP-23 — long-form notes; zap them through the a coordinate tag.
  • NIP-47 — Nostr Wallet Connect; lets a client pay the zap invoice through a remote wallet.
  • NIP-19 — bech32 encodings; the lnurl tag uses the same bech32 alphabet with an lnurl prefix.