Announce HTTP 402 services on Nostr for decentralised discovery. Kind 31402 parameterised replaceable events.
402-announce is an early-stage TypeScript project in the AI payments / x402 ecosystem, focused on 402, ai-agents, cashu, discovery. It currently has 1 GitHub stars and 0 forks, and sits alongside related tools like 402-mcp, toll-booth, satgate, routeweiler-python-sdk, inflow-node, x402-wiki.
Nostr: npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2
Announce HTTP 402 services on Nostr for decentralised discovery. Supports both L402 and x402 payment protocols.
Publishes kind 31402 parameterised replaceable events so that AI agents (and any Nostr client) can discover paid APIs without a central registry.
Live Dashboard — see every service announcing on the network right now.
flowchart LR
A[Your API] -->|"npm install<br/>402-announce"| B[402-announce]
B -->|"kind 31402<br/>signed event"| C[(Nostr Relays)]
C -->|subscribe & filter| D[402-mcp]
D -->|discover + pay| A
style B fill:#f59e0b,color:#000
style C fill:#8b5cf6,color:#fff
style D fill:#3b82f6,color:#fff
Your API publishes a service announcement to Nostr relays. AI agents (via 402-mcp) discover it, pay the invoice, and consume the API. No central registry required.
npm install 402-announce
import { announceService } from '402-announce'
const handle = await announceService({
secretKey: '64-char-hex-nostr-secret-key',
relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
identifier: 'jokes-api',
name: 'Jokes API',
urls: ['https://jokes.example.com'],
about: 'A joke-telling service behind an L402 paywall',
pricing: [
{ capability: 'get_joke', price: 1, currency: 'sats' },
],
paymentMethods: [['l402', 'lightning'], ['cashu'], ['xcashu']],
topics: ['comedy', 'ai'],
capabilities: [
{ name: 'get_joke', description: 'Returns a random joke' },
],
version: '1.0.0',
})
console.log('Published event:', handle.eventId)
console.log('From pubkey:', handle.pubkey)
// Later, when shutting down:
handle.close()
Multi-transport example (clearnet + Tor + Handshake — same service, different access paths):
const handle = await announceService({
secretKey: '64-char-hex-nostr-secret-key',
relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
identifier: 'jokes-api',
name: 'Jokes API',
urls: [
'https://jokes.example.com', // clearnet
'http://jokesxyz...onion', // Tor hidden service
'https://jokes.example.hns', // Handshake domain
],
about: 'A joke-telling service behind an L402 paywall',
pricing: [{ capability: 'get_joke', price: 1, currency: 'sats' }],
paymentMethods: [['l402', 'lightning']],
topics: ['comedy', 'ai'],
})
Each URL becomes a separate url tag in the kind 31402 event. Clients try them in order and use whichever they can reach. urls accepts 1–10 entries; any parseable URL is valid.
See examples/ for runnable scripts.
flowchart LR
subgraph "Your Server"
API[Your API]
TB[toll-booth<br/>L402 paywall]
AN[402-announce]
end
subgraph "Nostr Network"
R1[(Relay 1)]
R2[(Relay 2)]
end
subgraph "AI Agent"
MCP[402-mcp<br/>discovery + payment]
end
API --> TB
TB -->|"protects"| API
AN -->|"kind 31402"| R1
AN -->|"kind 31402"| R2
R1 -->|"subscribe"| MCP
R2 -->|"subscribe"| MCP
MCP -->|"HTTP + L402 token"| TB
style TB fill:#ef4444,color:#fff
style AN fill:#f59e0b,color:#000
style MCP fill:#3b82f6,color:#fff
close() handle for clean disconnectionannounceService(config): Promise<Announcement>High-level function that builds, signs, and publishes the event to multiple relays.
Returns an Announcement handle:
eventId — the published Nostr event IDpubkey — the Nostr pubkey derived from your secret keyclose() — disconnect from all relaysbuildAnnounceEvent(secretKey, config): VerifiedEventLower-level function that builds and signs the event without publishing. Useful if you manage relay connections yourself.
| Field | Type | Required | Description |
|---|---|---|---|
secretKey |
string |
yes | 64-char hex Nostr secret key |
relays |
string[] |
yes | Relay URLs (wss:// or ws://) |
identifier |
string |
yes | Unique listing ID (Nostr d tag) |
name |
string |
yes | Human-readable service name |
urls |
string[] |
yes | HTTP endpoints for the service (1–10 entries, any parseable URL) |
about |
string |
yes | Short description |
pricing |
PricingDef[] |
yes | Per-capability pricing |
paymentMethods |
string[][] |
yes | Accepted payment methods (each entry is [rail, ...params]) |
picture |
string |
no | Icon URL |
topics |
string[] |
no | Topic tags for filtering |
capabilities |
CapabilityDef[] |
no | Capability details (stored in event content) |
version |
string |
no | Service version (stored in event content) |
flowchart TD
A[announceService config] --> B{Validate}
B -->|"secretKey: 64-char hex"| C[Derive pubkey]
B -->|"relays: wss:// URLs"| C
C --> D[Build kind 31402 event]
D --> E[Sign with secret key]
E --> F[Zeroise key bytes]
F --> G["Connect relays (parallel, 10s timeout)"]
G --> H{Each relay}
H -->|success| I[Publish event]
H -->|failure| J[Log warning, continue]
I --> K["Return { eventId, pubkey, close() }"]
J --> K
Each announcement is a kind 31402 parameterised replaceable event. The combination of pubkey + d tag uniquely identifies a listing — publishing again with the same values updates the existing listing.
graph TB
subgraph "Kind 31402 Event"
direction TB
subgraph Tags
D["d: jokes-api"]
N["name: Jokes API"]
U1["url: https://jokes.example.com"]
U2["url: https://jokesapi.example.onion (optional)"]
AB["about: A joke-telling service"]
PMI1["pmi: l402, lightning"]
PMI2["pmi: cashu"]
P["price: get_joke, 1, sats"]
T["t: comedy"]
end
subgraph Content["Content (JSON)"]
CAP["capabilities: [{ name, description }]"]
VER["version: 1.0.0"]
end
end
style Tags fill:#1e293b,color:#e2e8f0
style Content fill:#1e293b,color:#e2e8f0
| Tag | Required | Description | Example |
|---|---|---|---|
d |
yes | Unique identifier for this listing | jokes-api |
name |
yes | Human-readable service name | Jokes API |
url |
yes | HTTP endpoint (one tag per URL; repeatable) | https://jokes.example.com |
about |
yes | Short description | A joke-telling service |
pmi |
yes | Payment method identifier (repeatable, multi-element) | l402, lightning |
price |
yes | Capability pricing (repeatable) | get_joke, 1, sats |
t |
no | Topic tag for search/filtering (repeatable) | comedy |
picture |
no | Icon URL | https://example.com/icon.png |
Each paymentMethods entry is an array where the first element is the rail identifier and subsequent elements are rail-specific parameters.
| Rail | Example | Description |
|---|---|---|
l402 |
['l402', 'lightning'] |
L402 protocol (Lightning BOLT-11 invoices) |
cashu |
['cashu'] |
Cashu ecash (generic) |
xcashu |
['xcashu'] |
Cashu ecash via NUT-24 (X-Cashu header) |
x402 |
['x402', 'base', 'usdc', '0xabc...'] |
x402 stablecoin payments (chain, token, receiver address) |
payment |
['payment', 'lightning'] |
IETF Payment protocol |
The event content is a JSON object with optional fields:
{
"capabilities": [
{ "name": "get_joke", "description": "Returns a random joke" }
],
"version": "1.0.0"
}
This distinction is important for operators:
Multiple URLs in one event — use urls: ['...', '...'] when the URLs represent the same service on different transports (clearnet, Tor, Handshake). The pricing, credentials, and macaroon signing key are identical. Clients pick whichever URL they can reach. This is for censorship resistance and redundancy.
Separate kind 31402 events — publish a new event (different identifier) when you have genuinely different services: different pricing tiers per transport, different capabilities, or services that operate independently. A single event should describe one logical service.
In short: same service + different network paths → one event with multiple url tags. Different services → separate events.
announceService and buildAnnounceEvent)| Package | Purpose |
|---|---|
| toll-booth | L402 middleware — any API becomes a toll booth in minutes |
| toll-booth-announce | Bridge — announce toll-booth services with one function call |
| satgate | Production L402 gateway with Lightning and Cashu support |
| 402-mcp | MCP server for AI agents to discover, pay, and consume 402 APIs |
| Live Dashboard | See every service announcing on the network |
ForgeSworn builds open-source cryptographic identity, payments, and coordination tools for Nostr.
| Library | What it does |
|---|---|
| nsec-tree | Deterministic sub-identity derivation |
| ring-sig | SAG/LSAG ring signatures on secp256k1 |
| range-proof | Pedersen commitment range proofs |
| canary-kit | Coercion-resistant spoken verification |
| spoken-token | Human-speakable verification tokens |
| toll-booth | L402 payment middleware |
| geohash-kit | Geohash toolkit with polygon coverage |
| nostr-attestations | NIP-VA verifiable attestations |
| dominion | Epoch-based encrypted access control |
| nostr-veil | Privacy-preserving Web of Trust |
402 client MCP - AI agents discover, pay for, and consume L402 + x402 APIs
Any API becomes a Lightning toll booth in one line. L402 middleware for Express, Hono, Deno, Bun, and Workers.
Lightning-paid AI inference - monetise any OpenAI-compatible endpoint in 30 seconds
The financial operating system for AI agents. A single HTTP client that intercepts '402 Payment Required', routes across x402, L402, and MPP, persists credentials as recoverable assets, enforces per-envelope budgets, and emits a structured trace.
Official TypeScript/Node.js SDKs for the InFlow payments platform - x402 and MPP (Machine Payments Protocol) integration for sellers and buyers.
The x402 Service Encyclopedia — Every service ranked, reviewed, and documented. Community knowledge base for the AI agent economy.
540 security tests for AI agent systems — MCP, A2A, x402/L402, decision governance, benchmark integrity, skill supply chain. AIUC-1 pre-cert, NIST AI 800-2 aligned, MCP tool-poisoning reproduction. v4.9.1
A neutral landscape analysis of AI agent payment protocols (x402, MPP, L402, Google AP2, Visa TAP + ICC, Mastercard Agent Pay, Amex ACE, Google/Shopify UCP, OpenAI ACP, and others). Maintained by Genesis Software Group, Copenhagen. Updated April 2026. CC BY 4.0.
MCP Server for Lightning Faucet - Give your AI agent a Bitcoin wallet
Give your AI agents a fetch() that pays. Multi-protocol, multi-chain, open source.
MCP server for 402 Index: discover 15,000+ paid API endpoints across L402, x402, and MPP
Sovereign oracle protocol — cryptographically signed data over Lightning sats (L402) and USDC on Base (x402). 11 feeds, 9 exchanges, DLC attestations, MCP server. No API keys, no accounts, no trust.