Menu
NIP-nip-57 Final

NIP-57: Lightning Zaps

Lightning payments through Nostr. Send and receive zaps with cryptographic proof of payment.

Type Nostr Implementation Possibility
Number nip-57
Status Final
Original https://github.com/nostr-protocol/nips/blob/master/57.md

NIP-57: Lightning Zaps

Status: Final

NIP-57 defines zaps—Lightning payments sent through Nostr with cryptographic proof. Zaps combine social interaction with value transfer.

Overview

┌─────────┐     ┌─────────────┐     ┌─────────────┐
│  Alice  │     │ LNURL Server│     │   Relays    │
│ (Zapper)│     │ (Bob's)     │     │             │
└────┬────┘     └──────┬──────┘     └──────┬──────┘
     │                 │                   │
     │ 1. Zap Request  │                   │
     │────────────────►│                   │
     │                 │                   │
     │ 2. Invoice      │                   │
     │◄────────────────│                   │
     │                 │                   │
     │ 3. Pay Invoice  │                   │
     │────────────────►│                   │
     │                 │                   │
     │                 │ 4. Zap Receipt    │
     │                 │───────────────────►
     │                 │                   │

Event Kinds

KindNamePurpose
9734Zap RequestRequest sent to LNURL server
9735Zap ReceiptProof of payment (published by server)

Setting Up to Receive Zaps

1. Get a Lightning Address

From a provider that supports NIP-57:

  • Alby (getalby.com)
  • Wallet of Satoshi
  • LNbits (self-hosted)
  • Zeus (self-custodial)

2. Update Profile

Add lud16 (Lightning Address) to kind 0:

{
  "kind": 0,
  "content": "{\"name\":\"Alice\",\"lud16\":\"alice@getalby.com\"}"
}

Or lud06 (raw LNURL):

{
  "content": "{\"name\":\"Alice\",\"lud06\":\"LNURL1...\"}"
}

3. Verify NIP-57 Support

Your LNURL endpoint must include allowsNostr: true:

curl https://getalby.com/.well-known/lnurlp/alice
{
  "callback": "https://getalby.com/lnurlp/alice/callback",
  "maxSendable": 10000000000,
  "minSendable": 1000,
  "allowsNostr": true,
  "nostrPubkey": "server-pubkey-hex"
}

Sending Zaps

Step 1: Get Recipient’s LNURL

async def get_recipient_lnurl(pubkey: str, relays: list) -> str:
    """Fetch recipient's Lightning address from profile."""
    profile = await fetch_profile(pubkey, relays)
    if not profile:
        return None

    content = json.loads(profile["content"])

    lud16 = content.get("lud16")
    if lud16:
        name, domain = lud16.split("@")
        return f"https://{domain}/.well-known/lnurlp/{name}"

    lud06 = content.get("lud06")
    if lud06:
        return decode_lnurl(lud06)

    return None

Step 2: Verify NIP-57 Support

async def get_lnurl_params(lnurl_endpoint: str) -> dict:
    """Fetch LNURL parameters."""
    async with httpx.AsyncClient() as client:
        response = await client.get(lnurl_endpoint)
        params = response.json()

    if not params.get("allowsNostr"):
        raise ValueError("LNURL doesn't support zaps")

    return params

Step 3: Create Zap Request (Kind 9734)

def create_zap_request(
    sender_pubkey: str,
    recipient_pubkey: str,
    amount_msats: int,
    relays: list,
    event_id: str = None,
    content: str = ""
) -> dict:
    """Create a NIP-57 zap request."""
    tags = [
        ["relays", *relays],
        ["amount", str(amount_msats)],
        ["p", recipient_pubkey]
    ]

    # Optional: zap specific event
    if event_id:
        tags.append(["e", event_id])

    # Optional: lnurl tag
    # tags.append(["lnurl", lnurl_bech32])

    return {
        "kind": 9734,
        "pubkey": sender_pubkey,
        "created_at": int(time.time()),
        "tags": tags,
        "content": content  # Optional zap comment
    }

Step 4: Request Invoice

async def request_zap_invoice(
    lnurl_callback: str,
    zap_request: dict,
    amount_msats: int
) -> str:
    """Request invoice from LNURL server."""
    # Sign the zap request
    signed_request = sign_event(zap_request, PRIVATE_KEY)

    # URL-encode the request
    request_json = json.dumps(signed_request)
    encoded = urllib.parse.quote(request_json)

    # Request invoice
    url = f"{lnurl_callback}?amount={amount_msats}&nostr={encoded}"

    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        data = response.json()

    if "reason" in data:
        raise ValueError(f"Error: {data['reason']}")

    return data["pr"]  # BOLT11 invoice

Step 5: Pay Invoice

async def pay_invoice(invoice: str, lnbits_url: str, admin_key: str):
    """Pay the BOLT11 invoice."""
    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"{lnbits_url}/api/v1/payments",
            headers={"X-Api-Key": admin_key},
            json={"out": True, "bolt11": invoice}
        )
        return response.json()

Complete Zap Flow

async def send_zap(
    recipient_pubkey: str,
    amount_sats: int,
    event_id: str = None,
    comment: str = ""
) -> dict:
    """Send a complete zap."""
    amount_msats = amount_sats * 1000

    # 1. Get recipient LNURL
    lnurl = await get_recipient_lnurl(recipient_pubkey, RELAYS)
    if not lnurl:
        raise ValueError("No Lightning address")

    # 2. Get LNURL params
    params = await get_lnurl_params(lnurl)

    # 3. Validate amount
    if amount_msats < params["minSendable"]:
        raise ValueError(f"Minimum: {params['minSendable']} msats")
    if amount_msats > params["maxSendable"]:
        raise ValueError(f"Maximum: {params['maxSendable']} msats")

    # 4. Create zap request
    zap_request = create_zap_request(
        sender_pubkey=MY_PUBKEY,
        recipient_pubkey=recipient_pubkey,
        amount_msats=amount_msats,
        relays=RELAYS,
        event_id=event_id,
        content=comment
    )

    # 5. Get invoice
    invoice = await request_zap_invoice(
        params["callback"],
        zap_request,
        amount_msats
    )

    # 6. Pay
    result = await pay_invoice(invoice, LNBITS_URL, ADMIN_KEY)

    return result

Zap Receipt (Kind 9735)

Published by the LNURL server after payment:

{
  "kind": 9735,
  "pubkey": "lnurl-server-pubkey",
  "created_at": 1234567890,
  "tags": [
    ["p", "recipient-pubkey"],
    ["e", "zapped-event-id"],
    ["P", "sender-pubkey"],
    ["bolt11", "lnbc..."],
    ["description", "<zap-request-json>"],
    ["preimage", "payment-preimage"]
  ],
  "content": ""
}

Tag Reference

TagContent
pRecipient pubkey
eZapped event ID (optional)
PSender pubkey (uppercase)
bolt11The paid invoice
descriptionOriginal zap request JSON
preimagePayment preimage (proof)

Receiving Zaps

Monitor Incoming Zaps

async def monitor_zaps(my_pubkey: str):
    """Subscribe to incoming zaps."""
    filter = {
        "kinds": [9735],
        "#p": [my_pubkey]
    }

    async for event in subscribe(RELAYS, filter):
        zap = parse_zap_receipt(event)
        print(f"Received {zap['amount_sats']} sats!")
        print(f"From: {zap['sender']}")
        if zap['comment']:
            print(f"Comment: {zap['comment']}")

Parse Zap Receipt

def parse_zap_receipt(event: dict) -> dict:
    """Parse a zap receipt (kind 9735)."""
    tags = {}
    for tag in event["tags"]:
        tags[tag[0]] = tag[1] if len(tag) > 1 else None

    # Get zap request from description
    description = tags.get("description", "{}")
    zap_request = json.loads(description) if description else None

    # Get amount from bolt11
    bolt11 = tags.get("bolt11", "")
    amount_msats = decode_bolt11_amount(bolt11) if bolt11 else 0

    return {
        "receipt_id": event["id"],
        "amount_msats": amount_msats,
        "amount_sats": amount_msats // 1000,
        "sender": tags.get("P"),  # Uppercase P = sender
        "recipient": tags.get("p"),  # Lowercase p = recipient
        "zapped_event": tags.get("e"),
        "comment": zap_request.get("content", "") if zap_request else "",
        "preimage": tags.get("preimage"),
        "timestamp": event["created_at"]
    }

Zap Splits

Distribute a zap among multiple recipients:

{
  "kind": 9734,
  "tags": [
    ["relays", "wss://relay.damus.io"],
    ["amount", "10000"],
    ["p", "pubkey1", "wss://relay", "0.7"],
    ["p", "pubkey2", "wss://relay", "0.3"]
  ]
}

The weight values (0.7, 0.3) indicate split ratios.

Verification

Always verify zap receipts:

def verify_zap_receipt(receipt: dict) -> bool:
    """Verify a zap receipt is legitimate."""
    # 1. Verify signature
    if not verify_event_signature(receipt):
        return False

    # 2. Get zap request from description
    description = get_tag(receipt, "description")
    if not description:
        return False

    zap_request = json.loads(description)

    # 3. Verify zap request signature
    if not verify_event_signature(zap_request):
        return False

    # 4. Verify amounts match
    request_amount = int(get_tag(zap_request, "amount") or 0)
    bolt11 = get_tag(receipt, "bolt11")
    invoice_amount = decode_bolt11_amount(bolt11)

    if request_amount != invoice_amount:
        return False

    # 5. Verify recipient matches
    receipt_recipient = get_tag(receipt, "p")
    request_recipient = get_tag(zap_request, "p")

    if receipt_recipient != request_recipient:
        return False

    return True

Agent Use Cases

Accept Zaps for Services

async def handle_zap(zap):
    """Process incoming zap for service."""
    if zap["amount_sats"] >= MIN_PAYMENT:
        # Extract request from comment
        if zap["comment"].startswith("/query"):
            query = zap["comment"][7:]
            result = await process_query(query)
            await post_reply(zap["zapped_event"], result)

Automated Tipping

async def auto_zap_quality_content(event):
    """Auto-zap content that meets quality threshold."""
    score = await evaluate_content(event)
    if score > 0.8:
        await send_zap(
            event["pubkey"],
            amount_sats=21,
            event_id=event["id"],
            comment="Great content!"
        )

Machine-Readable Summary

{
  "nip": 57,
  "title": "Lightning Zaps",
  "status": "final",
  "defines": [
    "zap-request-format",
    "zap-receipt-format",
    "lnurl-integration",
    "zap-verification"
  ],
  "event_kinds": {
    "zap_request": 9734,
    "zap_receipt": 9735
  },
  "required_lnurl_fields": [
    "allowsNostr",
    "nostrPubkey"
  ],
  "zap_request_tags": ["relays", "amount", "p", "e"],
  "zap_receipt_tags": ["p", "e", "P", "bolt11", "description", "preimage"],
  "related": [
    "/learn/nostr/zaps",
    "/learn/lightning/api-lnurl",
    "/learn/lightning/invoices"
  ]
}