ZapAPI Docs
Get StartedConceptsAI GatewayGuidesPayment GatewaySDKAPI ReferenceResources
Payment Gateway

Nostr Integration

NIP-05 identity, NIP-57 zaps (full spec) and NIP-47/NWC subscriptions.

Nostr is our identity + social payment layer. Three protocols matter: NIP-05 (identity), NIP-57 (zaps), NIP-47/NWC (wallet connect for subscriptions).

Library: nostr-tools (JS/TS, reference impl).


1. NIP-05 — Verifiable Identity (user@zapapi.com)

Endpoint

GET /.well-known/nostr.json?name=alice
 {
 "names": { "alice": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" },
 "relays": {
 "3bf0c6…459d": ["wss://relay.damus.io", "wss://nos.lol"]
 }
 }

Rules

  • Username on NIP-05 must equal Lightning Address username — one identity everywhere
  • User sets their own pubkey (npub or hex); we verify by challenge: user signs a NIP-98 event or a kind-0 note mentioning zapapi_verification:<code>
  • Server stores pubkey only, never nsec
  • Optional: offer to generate a keypair client-side for Nostr-newcomers (nsec shown once, never sent to server)

Value

  • Instant "verified" badge in every Nostr client
  • Free trust signal for bio pages

2. NIP-57 — Zaps (the core creator primitive)

How zaps work (research recap)

  1. Sender's client fetches our LNURL-pay endpoint, sees allowsNostr: true + nostrPubkey
  2. Sender creates kind 9734 (zap request): signed event with amount, relays, target pubkey/note
  3. Client calls our callback with nostr=<urlencoded 9734 json>
  4. We validate, create invoice with description_hash = sha256(9734 json)
  5. Sender pays. We publish kind 9735 (zap receipt) to the relays from the request
  6. Every Nostr client renders the zap under the note/profile — public social proof

Validation checklist (from NIP-57 spec)

  • [] Valid schnorr signature on 9734
  • [] amount tag == amount query param
  • [] relays tag present (non-empty)
  • [] p tag == recipient pubkey registered on ZapAPI
  • [] Invoice amount within min/max sendable
  • [] Reject expired/future created_at (±15 min window)

Building the invoice

import { sha256 } from '@noble/hashes/sha256';

const zapRequestJson = JSON.stringify(zapRequestEvent); // exact raw string from query!
const descriptionHash = Buffer.from(sha256(new TextEncoder().encode(zapRequestJson)))
.toString('hex');

// LNbits: POST /api/v1/payments with { out:false, amount: sats, description_hash, unhashed_description? }
// LNbits supports description_hash directly on invoice creation.

Publishing the receipt (kind 9735)

const receipt = {
 kind: 9735,
 pubkey: recipientPubkey, // MUST equal the p tag
 created_at: Math.floor(Date.now()/1000),
 tags: [
...zapRequest.tags.filter(t => ['p','e','a'].includes(t[0])),
 ['bolt11', bolt11Invoice],
 ['preimage', paymentPreimage],
 ['description', zapRequestJson],
],
 content: '',
};
// SIGNING: see §4 key strategy
await pool.publish(relaysFromZapRequest, signedReceipt);

Receipt signing strategy — IMPLEMENTED (A + B):

StrategyHowStatus
A. Platform zap keyReceipts signed server-side by NOSTR_ZAP_SIGNING_KEY; its pubkey is advertised as nostrPubkey in LNURL params (NIP-57 requires signer == advertised). Same model as Wallet of Satoshi.** Live (fallback)**
B. NIP-46 remote signer (nsecBunker)User connects a bunker:// signer (nsecBunker/Amber/Alby) from Dashboard Identity Remote Signer; each receipt is signed by the USER's key via kind 24133. URI stored AES-256 encrypted; key never touches our servers.** Live (preferred)**
C. NIP-26 delegationDelegation token for kind 9735 onlySkip (rarely supported)

Resolution order in ZapsService.publishReceipt: user signer (B) platform key (A) skip when neither is configured. Signer errors (unreachable bunker) are recorded on the connection and fall back to A automatically.

Receipts render fine in Damus/Primal because the description tag carries the original zap request. Implementation in server/src/nostr/: NostrService (NIP-01 sign/publish — verified against relay.damus.io), ZapsService (record + build 9735), ZapReceiptProcessor (30s cron settlepublish with retry ladder, 24h give-up). The zaps table tracks the full lifecycle: pending paid receipt_published / failed.

Zap analytics (creator dashboard)

  • Total zaps, sats received, top zappers, zaps per note
  • Feed from our DB (we see every zap request) + receipt confirmations

3. NIP-47 — Nostr Wallet Connect (subscriptions backbone)

Two directions:

A. Follower's wallet connects TO us (auto-pay their subscription)

  • Follower pastes nostr+walletconnect://<wallet-pubkey>?relay=…&secret=… from Alby Hub / other NWC wallet, with a monthly budget set on their side
  • Each cycle we send NWC request event (kind 23194, encrypted w/ NIP-04/NIP-44): { method: "pay_invoice", params: { invoice: <our renewal bolt11> } }
  • Wallet auto-pays within budget we get response (kind 23195) cycle marked paid

B. We act as NWC wallet service for OUR custodial wallets (Phase 4)

  • Give each ZapAPI wallet a NWC connection string users plug it into any NWC app
  • Server handles NWC requests over our relay, enforced by per-connection budgets
  • This is how we later enable "AI agents pay via ZapAPI wallet"

Relays for NWC

  • Run our own relay (e.g. nrelay/strfry) for NWC traffic: reliability + privacy
  • Default public set for profiles/receipts: damus, nos.lol, nostr.wine, primal

4. Server-side Nostr Service Design

server/src/nostr/
 nostr.service.ts // relay pool, publish, sign (managed keys)
 nip05.controller.ts // /.well-known/nostr.json
 zaps.service.ts // 9734 validation, receipt building
 nwc.service.ts // NIP-47 client (subscriptions) + later service mode
 nostr-keys.service.ts // managed zap-signing keys (per strategy A)
 entities/nostr-identity.entity.ts
// env
NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://relay.zapapi.com
NOSTR_ZAP_SIGNING_NSEC=<platform zap key for strategy A> // HSM/KMS in prod
NWC_RELAY=wss://relay.zapapi.com

Key management

  • Platform zap-signing key AWS KMS / env-injected, rotatable
  • Per-user: pubkeys only
  • If we ever generate keys for users client-side only (nostr-tools in browser)

5. What users experience

Influencer

  1. Signs up claims alice@zapapi.com
  2. Pastes npub (or verifies existing) NIP-05 badge everywhere on Nostr
  3. Followers zap from any client receipts show publicly sats in dashboard instantly
  4. Bio page shows "Recent supporters" (zap feed)

Follower

  1. Clicks on Alice's bio/note their wallet opens (LNURL/WebLN/NWC)
  2. Pays zap appears under the note in seconds
  3. For subscriptions: connects wallet once with a budget auto-supports monthly

Developer

  • Same endpoints work for any app: LNURL-pay + optional Nostr metadata
  • Webhook zap.received carries full zap request JSON for custom logic

On this page