---
title: Websockets
slug: websockets
docTags: 
createdAt: 2025-07-14T19:06:44.011Z
---

# Monitoring  with LND's Websockets

LND’s WebSocket interface provides real-time streaming for specific endpoints, typically those with **"subscribe"** in their names (e.g., SubscribeInvoices, SubscribeChannelEvents). Not every API method supports WebSockets — only those designed for synchronous, long-lived event streams.

WebSocket support is especially useful in environments where low-latency updates are needed, such as dashboards, mobile apps, or browser-based monitoring tools. You can open a persistent WebSocket connection to receive updates as they happen, without the need for polling.

*Also note that in the official LND documentation Websocket examples are at the bottom of the code samples for"REST", they do not have their own tab.&#x20;***[EXAMPLE](https://lightning.engineering/api-docs/api/lnd/invoices/subscribe-single-invoice/)**

## Subscribe to single invoice

Listens for all updates on a single invoice identified by it's URL safe base64 hash

LND Method: [https://lightning.engineering/api-docs/api/lnd/invoices/subscribe-single-invoice/](https://lightning.engineering/api-docs/api/lnd/invoices/subscribe-single-invoice/)

:::CodeblockTabs
```javascript
// Subscribe to a single invoice over WebSocket
const WebSocket = require('ws');

// --- Configuration ---
const LND_HOST      = process.env.LND_HOST;      // e.g. "your-node.voltageapp.io"
const R_HASH        = process.env.R_HASH;        // Base64-encoded r_hash of the invoice
const MACAROON_HEX  = process.env.MACAROON_HEX;  // Hex-encoded admin/read macaroon

if (!LND_HOST || !R_HASH || !MACAROON_HEX) {
  console.error('❌  Please set LND_HOST, R_HASH, and MACAROON_HEX environment variables.');
  process.exit(1);
}

// Convert base64 to URL-safe base64
const toUrlSafeBase64 = (b64) => b64.replace(/\+/g, '-').replace(/\//g, '_');
const urlSafeHash = toUrlSafeBase64(R_HASH);

// --- WebSocket connection ---
// For streaming RPCs over REST, LND expects the `method=GET` query param.
// (:8080 is the default REST port for LND’s TLS proxy.)
const wsUrl = `wss://${LND_HOST}:8080/v2/invoices/subscribe/${urlSafeHash}?method=GET`;
console.log(`📡  Connecting to ${wsUrl} …`);

const ws = new WebSocket(wsUrl, {
  rejectUnauthorized: false,          // set to true if you’re using a trusted TLS cert
  headers: {
    'Grpc-Metadata-Macaroon': MACAROON_HEX,
  },
});

ws.on('open', () => {
  console.log('✅  WebSocket connected. Waiting for invoice updates…');
});

ws.on('message', (data) => {
  try {
    console.log('\n📥  Raw invoice message received:');
    console.log(data);

    // The REST proxy wraps each message in { result: <Invoice> }
    const payload = JSON.parse(data);
    const inv = payload.result ?? payload; // fallback if already flat

    console.log('\n📦  Parsed invoice:');
    console.dir(inv, { depth: null });

    console.log(`- State:       ${inv.state}`);
    console.log(`- Amount Paid: ${inv.amt_paid_sat} sats`);
    console.log(`- Settled:     ${inv.settled}`);

    if (inv.settled) {
      console.log('✅  Invoice has been settled! Closing WebSocket…');
      ws.close();
    }
  } catch (err) {
    console.error('❌  Failed to parse invoice update:', err.message);
  }
});

ws.on('error', (err) => {
  console.error('❌  WebSocket error:', err.message);
});

ws.on('close', () => {
  console.log('🔌  WebSocket connection closed.');
});
```

```python
import os, base64, json
from websocket import WebSocketApp

REST_URL = os.getenv("LND_REST_URL", "https://localhost:8080")
MACAROON = os.getenv("LND_MACAROON")
INVOICE_HASH_HEX = os.getenv("LND_INVOICE_HASH")
if not (REST_URL and MACAROON and INVOICE_HASH_HEX):
    raise EnvironmentError("Please set LND_REST_URL, LND_MACAROON, and LND_INVOICE_HASH env variables.")

# Convert invoice hash to URL-safe base64 for the path
invoice_hash_bytes = bytes.fromhex(INVOICE_HASH_HEX)
invoice_hash_b64 = base64.urlsafe_b64encode(invoice_hash_bytes).rstrip(b"=").decode()
ws_url = REST_URL.replace("https://", "wss://").replace("http://", "ws://")  # use WebSocket scheme
ws_url += f"/v2/invoices/subscribe/{invoice_hash_b64}"

# Define WebSocket event handlers
def on_open(ws):
    print(f"✅ WebSocket connected for invoice {INVOICE_HASH_HEX[:8]}…")

def on_message(ws, message):
    # LND sends invoice updates as JSON strings
    print(f"Invoice update received: {message}")
    # Optionally, parse the JSON:
    # data = json.loads(message)
    # print(f"Invoice state: {data.get('state')}, settled={data.get('settled')}")

def on_error(ws, error):
    print(f"WebSocket error: {error}")

def on_close(ws, status_code, msg):
    print("WebSocket connection closed.")

# Open the WebSocket connection
ws = WebSocketApp(ws_url, header=[f"Grpc-Metadata-macaroon: {MACAROON}"],
                  on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close)
# Note: If using a self-signed cert, you may disable SSL verification with sslopt in run_forever.
ws.run_forever()  # Blocks and listens for invoice updates
```

```php
<?php

require 'vendor/autoload.php';

use WebSocket\Client;

$LND_HOST = getenv('LND_HOST') ?: 'localhost:8080';
$MACAROON_HEX = bin2hex(file_get_contents(getenv('LND_MACAROON_PATH')));
$R_HASH = getenv('R_HASH_BASE64'); // Base64-encoded payment hash of invoice

// URL-safe Base64
function urlSafeBase64($base64) {
    return str_replace(['+', '/'], ['-', '_'], $base64);
}

$urlSafeHash = urlSafeBase64($R_HASH);

// Create WebSocket connection
$wsUrl = "wss://{$LND_HOST}/v2/invoices/subscribe/{$urlSafeHash}?method=GET";
$headers = ["Grpc-Metadata-Macaroon: {$MACAROON_HEX}"];

$client = new Client($wsUrl, [
    'headers' => $headers,
    'timeout' => 600,
    'ssl' => ['verify_peer' => false, 'verify_peer_name' => false]
]);

echo "📡 Connected to invoice subscription WebSocket.\n";

try {
    while ($msg = $client->receive()) {
        $invoice = json_decode($msg, true)['result'] ?? json_decode($msg, true);

        echo "📥 Invoice Update Received:\n";
        echo "- State: {$invoice['state']}\n";
        echo "- Amount Paid: {$invoice['amt_paid_sat']} sats\n";
        echo "- Settled: " . ($invoice['settled'] ? 'Yes' : 'No') . "\n";

        if ($invoice['settled']) {
            echo "✅ Invoice Settled! Closing WebSocket.\n";
            break;
        }
    }
} catch (Exception $e) {
    echo "Error: {$e->getMessage()}\n";
}

$client->close();
echo "🔌 WebSocket closed.\n";
```
:::

## Subscribe to set of invoices

Listens for **all** new and settled invoices starting at a fixed starting point (`add_index = 0`, `settle_index = 0`).

**add\_index — “new invoice offset”**

- Every time an invoice is *created* (added) LND assigns it the next add\_index (0, 1, 2 …). If you pass add\_index = N when you subscribe, LND first replays every invoice whose add\_index is **greater than N**—letting you catch up on any invoices that were created while you were offline.

**settle\_index — “settled invoice offset”:**

- Similarly, each time an invoice becomes *settled* (paid) it gets the next settle\_index.
  Supplying settle\_index = M makes LND replay all settlement events with a settle\_index **greater than M**, so you don’t miss any payments that arrived while you were disconnected.

Run this example and then create or pay an invoice on your node and you should see the message come in immediately.

LND Method: [https://lightning.engineering/api-docs/api/lnd/lightning/subscribe-invoices/](https://lightning.engineering/api-docs/api/lnd/lightning/subscribe-invoices/)

:::CodeblockTabs
```javascript
// Subscribe to a set of invoices over Websocket
const WebSocket = require('ws');

// --- Configuration ---
const LND_HOST = process.env.LND_HOST;
const MACAROON_HEX = process.env.MACAROON_HEX;

if (!LND_HOST || !MACAROON_HEX) {
  console.error('❌  Please set both LND_HOST and MACAROON_HEX environment variables.');
  process.exit(1);
}

// Hard‑coded subscription indices (start at the very beginning)
const ADD_INDEX = '0';
const SETTLE_INDEX = '0';

// --- WebSocket connection ---
// The REST proxy for streaming RPCs requires the `method=GET` query param.
const wsUrl = `wss://${LND_HOST}:8080/v1/invoices/subscribe?method=GET`;
console.log(`📡  Connecting to ${wsUrl} …`);

const ws = new WebSocket(wsUrl, {
  rejectUnauthorized: false, // set to true if you have a trusted TLS cert
  headers: {
    'Grpc-Metadata-Macaroon': MACAROON_HEX,
  },
});

ws.on('open', () => {
  console.log('✅  WebSocket connected. Subscribing…');

  // Send subscription request with fixed indices
  ws.send(JSON.stringify({ add_index: ADD_INDEX, settle_index: SETTLE_INDEX }));
});

ws.on('message', (data) => {
  try {
    // The REST proxy wraps each message in { result: <Invoice> }
    const payload = JSON.parse(data);
    const inv = payload.result ?? payload; // fallback if already flat

    console.log('\n📦  Invoice update received:');
    console.log(`  • Hash (r_hash):   ${inv.r_hash}`);
    console.log(`  • State:           ${inv.state}`);
    console.log(`  • Amount paid:     ${inv.amt_paid_sat} sats`);
    console.log(`  • Settled:         ${inv.settled}`);
  } catch (err) {
    console.error('❌  Failed to parse message:', err.message);
    console.log('Raw data:', data.toString());
  }
});

ws.on('error', (err) => {
  console.error('❌  WebSocket error:', err.message);
});

ws.on('close', (code, reason) => {
  console.log(`🔌  WebSocket closed (${code}) ${reason || ''}`);
});
```

```python
import os, base64, threading
from websocket import WebSocketApp

REST_URL = os.getenv("LND_REST_URL", "https://localhost:8080")
MACAROON = os.getenv("LND_MACAROON")
INVOICE_HASHES = os.getenv("LND_INVOICE_HASHES")  # comma-separated hex hashes for invoices
if not (REST_URL and MACAROON and INVOICE_HASHES):
    raise EnvironmentError("Please set LND_REST_URL, LND_MACAROON, and LND_INVOICE_HASHES env variables.")

invoice_list = [h.strip() for h in INVOICE_HASHES.split(",") if h.strip()]
ws_base = REST_URL.replace("https://", "wss://").replace("http://", "ws://") + "/v2/invoices/subscribe/"

def make_handlers(invoice_hex):
    short_id = invoice_hex[:8]  # short identifier for logs
    inv_bytes = bytes.fromhex(invoice_hex)
    inv_b64 = base64.urlsafe_b64encode(inv_bytes).rstrip(b"=").decode()
    url = ws_base + inv_b64

    def on_open(ws):
        print(f"🔗 Opened WS for invoice {short_id}…")
    def on_message(ws, message):
        print(f"[Invoice {short_id}…] Update: {message}")
    def on_error(ws, error):
        print(f"[Invoice {short_id}…] WS error: {error}")
    def on_close(ws, status_code, msg):
        print(f"[Invoice {short_id}…] WS closed.")
    # Return a WebSocketApp instance for this invoice
    return WebSocketApp(url, header=[f"Grpc-Metadata-macaroon: {MACAROON}"],
                        on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close)

# Launch a WebSocket for each invoice in separate threads
threads = []
for inv_hex in invoice_list:
    ws_app = make_handlers(inv_hex)
    t = threading.Thread(target=ws_app.run_forever, daemon=True)
    threads.append(t)
    t.start()
    time_str = ""  # (If needed, import time at top; here we assume it's imported elsewhere in this context.)

# Keep the main thread alive to allow background threads to run
print(f"Subscribed to {len(invoice_list)} invoices via WebSockets. Waiting for updates...")
try:
    import time
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print("Exiting invoice subscription monitor.")
```

```php
<?php
require 'vendor/autoload.php';

use WebSocket\Client;

$restHost    = getenv('REST_HOST') ?: 'localhost:8080';
$macaroonPath = getenv('LND_MACAROON_PATH') ?: '/path/to/admin.macaroon';

// Prepare the macaroon for header
$macaroonHex = trim(bin2hex(file_get_contents($macaroonPath)));

// Connect to LND's subscribeInvoices via WebSocket (wss)
$wsUrl = "wss://{$restHost}/v1/invoices/subscribe?method=GET";
$client = new Client($wsUrl, [
    'stream_context' => [
        'ssl' => [ 
            'verify_peer' => false,       // Accept self-signed cert for demo
            'verify_peer_name' => false 
        ]
    ],
    'headers' => [
        'Grpc-Metadata-macaroon' => $macaroonHex
    ]
]);

// Optionally, specify indices to start from (to avoid missing events)
// Here we send an empty subscription request (to get events from now on)
$requestBody = ['add_index' => 0, 'settle_index' => 0];
$client->send(json_encode($requestBody));
echo "Subscribed to invoice updates, waiting for events...\n";

// Continuously read messages (this loop runs indefinitely until the socket closes)
while (true) {
    try {
        $message = $client->receive();
    } catch (\WebSocket\ConnectionException $e) {
        echo "WebSocket error or connection closed: {$e->getMessage()}\n";
        break;
    }
    if ($message === null) {
        // No more messages or stream ended
        break;
    }
    // Parse and handle the invoice update
    $invoiceUpdate = json_decode($message, true);
    echo "📥 Invoice update received: add_index={$invoiceUpdate['add_index']}, settled=" 
         . ($invoiceUpdate['settled'] ? 'yes' : 'no') . "\n";
}
```
:::

## Subscribe to single payment

Listens for all updates on a single invoice identified by it's URL safe base64 hash

LND Method: [https://lightning.engineering/api-docs/api/lnd/router/track-payment-v2/](https://lightning.engineering/api-docs/api/lnd/router/track-payment-v2/)

:::CodeblockTabs
```javascript
// Track a single payment over WebSocket
const WebSocket = require('ws');

// --- Configuration -----------------------------
const LND_HOST      = process.env.LND_HOST;       // e.g. "your-node.voltageapp.io"
const PAYMENT_HASH  = process.env.PAYMENT_HASH;   // hex or base64 payment hash
const MACAROON_HEX  = process.env.MACAROON_HEX;   // admin macaroon in hex

if (!LND_HOST || !PAYMENT_HASH || !MACAROON_HEX) {
  console.error('❌  Please set LND_HOST, PAYMENT_HASH, and MACAROON_HEX env vars.');
  process.exit(1);
}

// Build WS URL:  TrackPaymentV2 lives under /v2/router/track/{payment_hash}
const wsUrl = `wss://${LND_HOST}:8080/v2/router/track/${PAYMENT_HASH}?method=GET`;
console.log(`📡  Connecting to ${wsUrl} …`);

const ws = new WebSocket(wsUrl, {
  rejectUnauthorized: false,           // set true if you use a trusted TLS cert
  headers: { 'Grpc-Metadata-Macaroon': MACAROON_HEX },
});

ws.on('open', () => {
  console.log('✅  WebSocket connected. Subscribing…');

  // For WS, the REST proxy expects an initial JSON body
  // (even though the hash is already in the path).
  ws.send(JSON.stringify({
    payment_hash: PAYMENT_HASH,
    no_inflight_updates: false,        // change to true for “only final outcome”
  }));
});

ws.on('message', (data) => {
  try {
    const payload = JSON.parse(data);
    const pay     = payload.result ?? payload;     // unwrap { result: … } if present

    console.log('\n💸  Payment update:');
    console.log(`  • Status:       ${pay.status}`);        // IN_FLIGHT | SUCCEEDED | FAILED
    console.log(`  • Fee (sat):    ${pay.fee_sat}`);
    console.log(`  • Paid (sat):   ${pay.value_sat}`);
    console.log(`  • Attempts:     ${pay.htlcs?.length ?? 0}`);

    if (pay.status === 'SUCCEEDED' || pay.status === 'FAILED') {
      console.log(`🏁  Payment ${pay.status === 'SUCCEEDED' ? 'succeeded' : 'failed'}.`);
      ws.close();
    }
  } catch (err) {
    console.error('❌  Failed to parse message:', err.message);
    console.log('Raw data:', data.toString());
  }
});

ws.on('error',  (err)      => console.error('❌  WebSocket error:',  err.message));
ws.on('close',  (code, r)  => console.log(`🔌  WebSocket closed (${code}) ${r || ''}`));
```

```python
import os, base64
from websocket import WebSocketApp

REST_URL = os.getenv("LND_REST_URL", "https://localhost:8080")
MACAROON = os.getenv("LND_MACAROON")
PAYMENT_HASH_HEX = os.getenv("LND_PAYMENT_HASH")
if not (REST_URL and MACAROON and PAYMENT_HASH_HEX):
    raise EnvironmentError("Please set LND_REST_URL, LND_MACAROON, and LND_PAYMENT_HASH env variables.")

# Convert payment hash to URL-safe base64
pay_hash_bytes = bytes.fromhex(PAYMENT_HASH_HEX)
pay_hash_b64 = base64.urlsafe_b64encode(pay_hash_bytes).rstrip(b"=").decode()
ws_url = REST_URL.replace("https://", "wss://").replace("http://", "ws://")
ws_url += f"/v2/router/track/{pay_hash_b64}"

def on_open(ws):
    print(f"✅ WebSocket connected for payment {PAYMENT_HASH_HEX[:8]}…")
def on_message(ws, message):
    print(f"Payment update received: {message}")
def on_error(ws, error):
    print(f"WebSocket error: {error}")
def on_close(ws, status_code, msg):
    print("WebSocket closed.")

ws = WebSocketApp(ws_url, header=[f"Grpc-Metadata-macaroon: {MACAROON}"],
                  on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close)
ws.run_forever()
```

```php
<?php
require 'vendor/autoload.php';

use WebSocket\Client;

$restHost     = getenv('REST_HOST') ?: 'localhost:8080';
$macaroonPath  = getenv('LND_MACAROON_PATH') ?: '/path/to/admin.macaroon';
$paymentHashHex = getenv('PAYMENT_HASH_HEX') ?: '<payment_hash_in_hex>';  // payment hash to track (hex)

// Read macaroon and connect to the trackPayment WebSocket endpoint
$macaroonHex = trim(bin2hex(file_get_contents($macaroonPath)));
$wsUrl = "wss://{$restHost}/v2/router/track/{$paymentHashHex}?method=GET";
$client = new Client($wsUrl, [
    'stream_context' => [
        'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false ]
    ],
    'headers' => [ 'Grpc-Metadata-macaroon' => $macaroonHex ]
]);

// Send initial request body (here we can disable inflight updates if we only want final result)
$requestBody = [ 'payment_hash' => $paymentHashHex, 'no_inflight_updates' => false ];
$client->send(json_encode($requestBody));
echo "Tracking payment {$paymentHashHex}...\n";

// Read streaming payment updates
while (true) {
    try {
        $message = $client->receive();
    } catch (\WebSocket\ConnectionException $e) {
        echo "Connection closed or error: {$e->getMessage()}\n";
        break;
    }
    if (!$message) {
        break;
    }
    $paymentUpdate = json_decode($message, true);
    $status = $paymentUpdate['status'] ?? '(unknown)';
    echo "💸 Payment update: status={$status}";
    if ($status === 'SUCCEEDED') {
        // If succeeded, you can retrieve the preimage and other details
        echo ", preimage={$paymentUpdate['payment_preimage']}\n";
        echo "✅ Payment succeeded, stopping tracking.\n";
        break;
    } elseif ($status === 'FAILED') {
        echo "\n❌ Payment failed, stopping tracking.\n";
        break;
    } else {
        echo " (in-progress)\n";
        // continue looping for more updates
    }
}
```
:::

## Subscribe to all outgoing payments

Tracks **all&#x20;**&#x6E;ew outgoing payments

- Start this script (ideally before sending any new payments).
- Use lncli sendpayment / SendPayment\* from your node *or* an app that routes through it.
- Watch each payment move through IN\_FLIGHT ➜ SUCCEEDED/FAILED in real time, complete with fee and attempt details.

LND Method: [https://lightning.engineering/api-docs/api/lnd/router/track-payments/](https://lightning.engineering/api-docs/api/lnd/router/track-payments/)

:::CodeblockTabs
```javascript
// Track *all* in-flight payments over WebSocket
const WebSocket = require('ws');

// --- Configuration ---------------------------------
const LND_HOST     = process.env.LND_HOST;        // e.g. "your-node.voltageapp.io"
const MACAROON_HEX = process.env.MACAROON_HEX;    // admin macaroon in hex
const ONLY_FINALS  = process.env.ONLY_FINALS === 'true'; // optional toggle

if (!LND_HOST || !MACAROON_HEX) {
  console.error('❌  Please set LND_HOST and MACAROON_HEX env vars.');
  process.exit(1);
}

// Build WS URL  — TrackPayments lives at /v2/router/payments
const wsUrl = `wss://${LND_HOST}:8080/v2/router/payments?method=GET`;
console.log(`📡  Connecting to ${wsUrl} …`);

const ws = new WebSocket(wsUrl, {
  rejectUnauthorized: false,           // set true if you use a trusted TLS cert
  headers: { 'Grpc-Metadata-Macaroon': MACAROON_HEX },
});

ws.on('open', () => {
  console.log('✅  WebSocket connected. Subscribing…');

  // Initial body (required by REST proxy even though there’s no path param)
  ws.send(JSON.stringify({
    no_inflight_updates: ONLY_FINALS,   // false = full progress, true = finals only
  }));
});

ws.on('message', (data) => {
  try {
    const payload = JSON.parse(data);
    const pay     = payload.result ?? payload; // unwrap { result: … } if needed

    console.log('\n💸  Payment event:');
    console.log(`  • Hash:         ${pay.payment_hash}`);
    console.log(`  • Status:       ${pay.status}`);          // IN_FLIGHT | SUCCEEDED | FAILED
    console.log(`  • Sent (sat):   ${pay.value_sat}`);
    console.log(`  • Fee  (sat):   ${pay.fee_sat}`);
    console.log(`  • Attempts:     ${pay.htlcs?.length ?? 0}`);

    // Optionally close when payment reaches a terminal state
    if (pay.status === 'SUCCEEDED' || pay.status === 'FAILED') {
      console.log(`🏁  Payment ${pay.status.toLowerCase()}.`);
      // Don’t close the socket — other payments might still be streaming!
    }
  } catch (err) {
    console.error('❌  Failed to parse message:', err.message);
    console.log('Raw data:', data.toString());
  }
});

ws.on('error',  (err)     => console.error('❌  WebSocket error:', err.message));
ws.on('close',  (code,r)  => console.log(`🔌  WebSocket closed (${code}) ${r||''}`));

```

```python
import os
from websocket import WebSocketApp

REST_URL = os.getenv("LND_REST_URL", "https://localhost:8080")
MACAROON = os.getenv("LND_MACAROON")
if not (REST_URL and MACAROON):
    raise EnvironmentError("Please set LND_REST_URL and LND_MACAROON env variables.")

ws_url = REST_URL.replace("https://", "wss://").replace("http://", "ws://") + "/v2/router/payments"

def on_open(ws):
    print("✅ WebSocket connected for all-payment subscription.")
def on_message(ws, message):
    print(f"Outgoing payment event: {message}")
def on_error(ws, error):
    print(f"WebSocket error: {error}")
def on_close(ws, status_code, msg):
    print("WebSocket closed.")

ws = WebSocketApp(ws_url, header=[f"Grpc-Metadata-macaroon: {MACAROON}"],
                  on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close)
ws.run_forever()
```

```php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$restHost    = getenv('REST_HOST') ?: 'localhost:8080';
$macaroonPath = getenv('LND_MACAROON_PATH') ?: '/path/to/admin.macaroon';

$macaroonHex = trim(bin2hex(file_get_contents($macaroonPath)));
$client = new Client([
    'base_uri' => "https://{$restHost}",
    'verify'   => false,
]);

// Poll for payments periodically
function pollAllPayments(int $intervalSec = 5) {
    global $client, $macaroonHex;
    while (true) {
        try {
            $response = $client->get('/v1/payments?include_incomplete=true', [
                'headers' => [ 'Grpc-Metadata-macaroon' => $macaroonHex ]
            ]);
            $paymentsData = json_decode((string)$response->getBody(), true);
            $payments = $paymentsData['payments'] ?? [];
            
            echo "Payments snapshot at ".date('Y-m-d H:i:s').":\n";
            foreach ($payments as $payment) {
                echo "- Hash: {$payment['payment_hash']} Status: {$payment['status']}\n";
            }

        } catch (\Exception $e) {
            echo "Polling error: {$e->getMessage()}\n";
        }

        sleep($intervalSec);
    }
}

pollAllPayments();
```
:::

