Paid relay
NostrFeed has two official relays. Do not replace the free one with the paid one.
| Role | URL | Who can write |
|---|---|---|
| Free public | wss://relay.nostrfeed.com | anyone |
| Paid write | wss://paid.nostrfeed.com | pubkey that paid 2100 sats once |
Keep DMs, zaps, NWC, bunker, and kind 10050 on the free relay. Use the paid relay as an extra write outbox after admission. Reads on the paid relay are open.
Do this in the NostrFeed app. Do not send the user to the website to pay.
Web fallback only: paid.nostrfeed.com/invoices
1. Discovery (once at app start)
GET https://api.getzap.me/v1/connect
disc.relay // wss://relay.nostrfeed.com — always connect
disc.inbox_relay // same, DMs / NWC / bunker
disc.paid_relay // wss://paid.nostrfeed.com
disc.admission.url // https://paid.nostrfeed.com
disc.admission.check // https://paid.nostrfeed.com/admissions/check/{pubkey}
disc.admission.invoices // https://paid.nostrfeed.com/invoices
disc.admission.amount_sats // 2100
disc.admission.once // true
Identity pack also has it: GET /identity?pubkey= returns relays.paid, admission, and a kind 10002 template.
2. Pay inside the client (no webpage)
Show a checkbox for the terms. Then create the invoice with JSON. Show QR / bolt11 / WebLN / NWC in your UI. Poll until admitted. Never open paid.nostrfeed.com in a browser.
const RELAY_HTTP = "https://paid.nostrfeed.com";
async function ensurePaidRelay(hexPubkey) {
const checkUrl = RELAY_HTTP + "/admissions/check/" + hexPubkey;
let { userAdmitted } = await fetch(checkUrl).then((r) => r.json());
if (userAdmitted) return;
const inv = await fetch(RELAY_HTTP + "/invoices", {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify({ pubkey: hexPubkey, tosAccepted: true, feeSchedule: "admission" }),
}).then((r) => r.json());
if (inv.userAdmitted) return;
// in-app: QR of lightning:inv.bolt11, copy, WebLN, NWC
showInvoice(inv.bolt11, inv.amount_sats);
if (window.webln) {
await window.webln.enable();
await window.webln.sendPayment(inv.bolt11);
}
while (true) {
await sleep(3000);
const st = await fetch(inv.status_url).then((r) => r.json());
const again = await fetch(checkUrl).then((r) => r.json());
if (st.status === "completed" || again.userAdmitted) return;
if (st.status === "expired") throw new Error("invoice expired");
}
}
connectRelay(disc.relay);
const pack = await fetch(BASE + "/identity?pubkey=" + hexPubkey).then((r) => r.json());
await signAndPublish(pack.setup, disc.relay);
await ensurePaidRelay(hexPubkey);
connectRelay(disc.paid_relay);
publishNotesTo([disc.relay, disc.paid_relay]);
Invoice JSON looks like:
{
"ok": true,
"userAdmitted": false,
"id": "payment-hash",
"bolt11": "lnbc21u1…",
"amount_sats": 2100,
"expires_at": "2026-08-21T07:15:00.000Z",
"status": "pending",
"relay": "wss://paid.nostrfeed.com",
"status_url": "https://paid.nostrfeed.com/invoices/{id}/status",
"check": "https://paid.nostrfeed.com/admissions/check/{pubkey}"
}
If the key already paid, POST returns userAdmitted: true and no bolt11. CORS is open for https://www.nostrfeed.com.
Fee info without creating an invoice:
GET https://paid.nostrfeed.com/invoices Accept: application/json
3. Relay list events
Kind 10002 from pack.setup.kind10002:
{
"kind": 10002,
"tags": [
["r", "wss://relay.nostrfeed.com"],
["r", "wss://paid.nostrfeed.com", "write"]
]
}
Kind 10050 (DM inbox) stays only the free relay:
{
"kind": 10050,
"tags": [["relay", "wss://relay.nostrfeed.com"]]
}
Publish those signed events to the free relay first. After admission, also publish notes to the paid relay.
4. Admission API
| What | Call |
|---|---|
| Pay page | GET https://paid.nostrfeed.com/invoices |
| Admitted? | GET /admissions/check/{hex} → {"userAdmitted": true|false} |
| Create invoice | POST /invoices form: pubkey, tosAccepted=yes, feeSchedule=admission |
| Invoice status | GET /invoices/{id}/status → pending|completed|expired |
| NIP-11 | GET / with Accept: application/nostr+json |
POST https://paid.nostrfeed.com/invoices Content-Type: application/x-www-form-urlencoded pubkey=npub1…&tosAccepted=yes&feeSchedule=admission
NIP-11 has limitation.payment_required: true and fees.admission: 2100000 msats.
Unpaid writes are rejected with blocked: pubkey not admitted. Kind 9735 zap receipts do not need admission.
5. What the client must not do
- Do not drop
wss://relay.nostrfeed.com - Do not send DMs, NWC, or bunker traffic to the paid relay
- Do not invent on-chain addresses. Admission is Lightning only
- Do not show the LNbits admin key in the app