Relay Manager Docs
Protocol

NIP-46 — Remote Signing

Handshake and proxy signing flow between relay-connect-web, relay-api, and the Signer Device.

NIP-46 — Remote Signing

For browser-extension signing on the same machine, see NIP-07.


NIP-46 allows a Signer Device (Amber, or any compatible app) to sign Nostr events on behalf of a client, without the client ever holding the private key. The bridge relay acts as the transport layer.


Actors

ActorRole
BrowserInitiates the flow; renders QR; polls for pairing
relay-connect-webNext.js server — proxies all /signer/* calls to the Web Server
relay-api (Web Server)Orchestrates sessions in Supabase; returns nostrconnect_uri
SupabasePersists nip46_sessions: pendingactiverevoked
Signer DeviceAmber or NIP-46 app; scans QR; connects to bridge
Bridge relaywss:// relay used as NIP-46 transport (not a Nostr content relay)

Handshake Flow


Session Lifecycle

INSERT (pending)


Signer Device scans URI + connects to bridge


Browser calls POST /signer/session/:id/complete with pairing_secret


UPDATE status = active

    ├── DELETE /signer/session/:id  →  status = revoked
    └── New connect flow  →  new session (fresh pairing_secret)

Reusing an active session without re-scanning is a product choice. relay-connect-web does not implement this by default — every Connect flow starts a new session with a new pairing_secret.


nostrconnect:// URI Structure

nostrconnect://<app_pubkey>?relay=<bridge_wss>&secret=<pairing_secret>&...
  • app_pubkey — the client's ephemeral public key
  • relay — the bridge wss:// where NIP-46 events will be exchanged
  • secret — the pairing_secret stored in nip46_sessions; verified on POST /complete

Supabase Table: relay.nip46_sessions

ColumnTypeDescription
iduuidPK
provider_user_idtextOperator's GitHub ID
relay_config_iduuidFK → relay_configs.id
app_pubkeytextClient ephemeral pubkey
bridge_wsstextTransport relay URL
pairing_secrettextVerified on /complete
statustextpending | active | revoked

Required migrations (in order):

  1. 20260324140000_relay_nip46_sessions.sql — DDL
  2. 20260325130000_relay_nip46_sessions_grants.sql — PostgREST role grants

Without the grants migration, INSERT from relay-api returns 42501 (permission denied for PostgREST anon / authenticated roles).


Web Server Endpoints

MethodRouteDescription
GET/signer/configList operator relays for provider_user_id
POST/signer/connectCreate session; returns nostrconnect_uri
GET/signer/sessionsList sessions (polling endpoint)
POST/signer/session/:id/completeVerify secret → active
DELETE/signer/session/:idRevoke session

Also mounted at /api/signer/* (same handlers, same auth).


Security Notes

  • pairing_secret is single-use per session. A new Connect flow always generates a fresh secret.
  • The browser never holds RELAY_API_KEY — all /signer/* calls go through the Next.js server proxy.
  • bridge_wss is operator-controlled. BitMacro default uses its own relay as bridge.
  • The Signer Device (Amber) holds the user's private key and never transmits it — only signed events travel over the bridge.