Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sandwichfarm/nostr-watch/llms.txt

Use this file to discover all available pages before exploring further.

The subscriptions endpoints let you receive real-time relay state updates without polling. You first create a subscription with a set of filter criteria, then connect to the Server-Sent Events stream to receive updates as monitors publish new relay check results. This pattern is useful for dashboards, alerting systems, and any application that needs fresh relay data without repeatedly polling the REST endpoints.
Subscriptions require the server-side FEATURE_SUBSCRIPTIONS feature flag to be enabled. On self-hosted instances, set this in your config.yaml or environment. The hosted nostr.watch API has subscriptions enabled.

POST /subscriptions

Create a new subscription. Returns a subscriptionId that you pass to the SSE stream endpoint. Request body:
filter
object
Response:
{
  "subscriptionId": "sub_a1b2c3d4e5f6"
}
subscriptionId
string
Unique identifier for this subscription. Pass this to GET /subscriptions/events.
curl -X POST https://api.nostr.watch/v2/subscriptions \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "urls": ["wss://relay.damus.io", "wss://relay.nostr.band"]
    }
  }'

GET /subscriptions/events

Connect to the Server-Sent Events stream for a subscription. The server sends a state-update event each time a relay’s aggregated state changes. Query parameters:
subscriptionId
string
required
The subscription ID returned from POST /subscriptions.
The connection stays open until the client disconnects. The server sends events with the event type state-update and a JSON payload. SSE event format:
event: state-update
data: {"relayUrl":"wss://relay.damus.io","type":"state-update","relay":{...}}
relayUrl
string
The relay URL that changed.
type
string
Event type. Currently state-update.
relay
object
The updated relay state object.

JavaScript SSE example

The browser’s native EventSource API handles reconnection automatically.
async function watchRelays() {
  const BASE_URL = 'https://api.nostr.watch/v2'

  // Step 1: create a subscription
  const { subscriptionId } = await fetch(`${BASE_URL}/subscriptions`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      filter: {
        urls: ['wss://relay.damus.io', 'wss://relay.nostr.band'],
      },
    }),
  }).then(r => r.json())

  // Step 2: open the SSE stream
  const eventSource = new EventSource(
    `${BASE_URL}/subscriptions/events?subscriptionId=${subscriptionId}`
  )

  // Step 3: handle incoming updates
  eventSource.addEventListener('state-update', (event) => {
    const update = JSON.parse(event.data)
    console.log(`Update for ${update.relayUrl}:`, update.relay)
  })

  eventSource.addEventListener('error', (event) => {
    console.error('SSE connection error:', event)
  })

  // Step 4: close when done
  // eventSource.close()
  return eventSource
}

Node.js SSE example

Use the eventsource npm package in Node.js environments that don’t have a native EventSource.
import EventSource from 'eventsource'

const BASE_URL = 'https://api.nostr.watch/v2'

async function streamRelayUpdates(relayUrls: string[]) {
  // Create subscription
  const { subscriptionId } = await fetch(`${BASE_URL}/subscriptions`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ filter: { urls: relayUrls } }),
  }).then(r => r.json())

  // Connect to SSE stream
  const es = new EventSource(
    `${BASE_URL}/subscriptions/events?subscriptionId=${subscriptionId}`
  )

  es.addEventListener('state-update', (event: MessageEvent) => {
    const update = JSON.parse(event.data)
    console.log(`[${new Date().toISOString()}] ${update.relayUrl} updated`)
    // Process update.relay ...
  })

  es.onerror = (err: Event) => {
    console.error('SSE error:', err)
    // EventSource automatically reconnects on connection loss
  }

  return es
}

Python SSE example

import requests
import sseclient
import json

BASE_URL = "https://api.nostr.watch/v2"

def watch_relays(relay_urls):
    # Create subscription
    response = requests.post(f"{BASE_URL}/subscriptions", json={
        "filter": {"urls": relay_urls}
    })
    subscription_id = response.json()["subscriptionId"]
    print(f"Subscription created: {subscription_id}")

    # Connect to SSE stream
    sse_url = f"{BASE_URL}/subscriptions/events?subscriptionId={subscription_id}"
    stream_response = requests.get(sse_url, stream=True)
    client = sseclient.SSEClient(stream_response)

    for event in client.events():
        if event.event == "state-update":
            update = json.loads(event.data)
            print(f"Update for {update['relayUrl']}")

watch_relays(["wss://relay.damus.io", "wss://relay.nostr.band"])

Reconnection and error handling

The EventSource API reconnects automatically on connection loss with exponential backoff. For production use, handle the error event to log issues:
eventSource.onerror = (event) => {
  if (eventSource.readyState === EventSource.CLOSED) {
    console.log('SSE connection closed — not reconnecting')
  } else {
    console.warn('SSE connection lost — browser will reconnect automatically')
  }
}
Subscription IDs are in-memory and do not persist across server restarts. If the server restarts, create a new subscription and reconnect to the SSE stream. The EventSource error event fires when this happens.
For relay state updates that don’t require real-time streaming, consider using the CVM relays/subscribe_state tool instead — it provides the same filtering options over the Nostr protocol without an HTTP connection. See the CVM tools reference for details.

Build docs developers (and LLMs) love