---
title: GRPC
slug: grpc
docTags: 
createdAt: 2025-07-14T19:08:39.124Z
---

# Monitoring with LND's GRPC interface

LND's gRPC interface provides the most efficient and feature-complete way to interact with your Lightning node. It offers bidirectional streaming, strongly-typed responses, and access to all LND functionality. gRPC is ideal for server-side applications that need low-latency, persistent connections with your node.

## Setup

First, you'll need to download the LND proto files and install the necessary dependencies:

:::CodeblockTabs
JS Setup (bash)

```bash
# Download the proto files for your LND version
curl -o lightning.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto

# Install dependencies (JavaScript)
npm install @grpc/grpc-js @grpc/proto-loader
```

Python Setup (bash)

```bash
# 1. Grab the proto files that match your LND version
#    (👇 this pulls the whole repo’s protos so you have Lightning, Router, Invoices, etc.)
git clone --depth 1 https://github.com/lightningnetwork/lnd.git
cd lnd/lnrpc

# 2. Install the Python gRPC toolchain and HTTP / WebSocket helpers
python -m pip install --upgrade \
    grpcio grpcio-tools protobuf \
    requests websocket-client
```

```php
<?php
// Ensure the gRPC extension is loaded and .proto classes are autoloaded
require 'vendor/autoload.php';

use Grpc\ChannelCredentials;
use Lnrpc\LightningClient;
use Invoicesrpc\InvoicesClient;
use Routerrpc\RouterClient;

// Environment and file paths
putenv('GRPC_SSL_CIPHER_SUITES=HIGH+ECDSA');  // required by LND for gRPC TLS
$grpcHost     = getenv('GRPC_HOST') ?: 'localhost:10009';
$tlsCertPath   = getenv('LND_TLS_CERT') ?: '/path/to/tls.cert';
$macaroonPath  = getenv('LND_MACAROON_PATH') ?: '/path/to/admin.macaroon';

// Read TLS certificate and macaroon
$tlsCert   = file_get_contents($tlsCertPath);
$macaroon  = file_get_contents($macaroonPath);
$metadataCallback = function($metadata) use ($macaroon) {
    // Add macaroon as hex in metadata for each call
    return ['macaroon' => [bin2hex($macaroon)]];
};
$credentials = ChannelCredentials::createSsl($tlsCert);
$opts = [
    'credentials'     => $credentials,
    'update_metadata' => $metadataCallback
];
```
:::

## Subscribe to Single Invoice

Monitor updates for a specific invoice using its payment 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
// grpc-single-invoice.js - Monitor a single invoice via gRPC streaming
const fs = require("fs");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

// --- Configuration ---
const LND_HOST = process.env.LND_HOST; // e.g., "your-node.voltageapp.io"
const MACAROON_PATH = process.env.MACAROON_PATH; // path to admin.macaroon
const PROTO_PATH = process.env.PROTO_PATH || "./lightning.proto";
const R_HASH = process.env.R_HASH; // Base64-encoded r_hash of the invoice

if (!LND_HOST || !MACAROON_PATH || !R_HASH) {
  console.error("❌ Please set LND_HOST, MACAROON_PATH, and R_HASH env vars.");
  process.exit(1);
}

// gRPC loader options
const loaderOptions = {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};

// Load proto definition
const packageDefinition = protoLoader.loadSync(PROTO_PATH, loaderOptions);

async function subscribeToSingleInvoice() {
  // Set up SSL cipher suites
  process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA";

  // Read and prepare macaroon
  const macaroonBytes = fs.readFileSync(MACAROON_PATH);
  const macaroonHex = macaroonBytes.toString("hex");

  // Create metadata with macaroon
  const metadata = new grpc.Metadata();
  metadata.add("macaroon", macaroonHex);

  // Create credentials
  const macaroonCreds = grpc.credentials.createFromMetadataGenerator(
    (_args, callback) => callback(null, metadata)
  );
  const sslCreds = grpc.credentials.createSsl();
  const credentials = grpc.credentials.combineChannelCredentials(
    sslCreds,
    macaroonCreds
  );

  // Create gRPC client
  const lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
  const lnrpc = lnrpcDescriptor.lnrpc;
  const client = new lnrpc.Invoices(
    `${LND_HOST}:10009`,
    credentials
  );

  console.log(`📡 Subscribing to invoice ${R_HASH}...`);

  // Subscribe to single invoice
  const call = client.subscribeSingleInvoice({
    r_hash: Buffer.from(R_HASH, "base64"),
  });

  call.on("data", (invoice) => {
    console.log("\n📦 Invoice update received:");
    console.log(`  • State:        ${invoice.state}`);
    console.log(`  • Amount Paid:  ${invoice.amt_paid_sat} sats`);
    console.log(`  • Settled:      ${invoice.settled}`);
    console.log(`  • Settle Date:  ${invoice.settle_date}`);

    if (invoice.settled) {
      console.log("✅ Invoice has been settled!");
      call.cancel();
    }
  });

  call.on("error", (err) => {
    if (err.code !== grpc.status.CANCELLED) {
      console.error("❌ Stream error:", err.message);
    }
  });

  call.on("end", () => {
    console.log("🔌 Stream ended.");
  });
}

// Run the subscription
subscribeToSingleInvoice().catch(console.error);
```

```python
import os, codecs, grpc
# Generated gRPC classes from protos:
import invoices_pb2 as invoicesrpc
import invoices_pb2_grpc as invoicesstub

GRPC_HOST = os.getenv("LND_GRPC_HOST", "localhost:10009")
TLS_CERT_PATH = os.getenv("LND_TLS_CERT", "tls.cert")
MACAROON_PATH = os.getenv("LND_MACAROON_PATH")  # path to admin macaroon file
INVOICE_HASH_HEX = os.getenv("LND_INVOICE_HASH")
if not (MACAROON_PATH and INVOICE_HASH_HEX):
    raise EnvironmentError("Please set LND_MACAROON_PATH and LND_INVOICE_HASH env variables.")

# Setup TLS and macaroon credentials for gRPC
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
cert = open(TLS_CERT_PATH, "rb").read()
ssl_creds = grpc.ssl_channel_credentials(cert)
macaroon_bytes = open(MACAROON_PATH, "rb").read()
macaroon_hex = codecs.encode(macaroon_bytes, "hex")  # hex string
def metadata_callback(context, callback):
    callback([("macaroon", macaroon_hex)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
channel = grpc.secure_channel(GRPC_HOST, combined_creds)

# Create a stub for the Invoices service
invoices_stub = invoicesstub.InvoicesStub(channel)

# Subscribe to a single invoice by its r_hash
invoice_hash_bytes = bytes.fromhex(INVOICE_HASH_HEX)
request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=invoice_hash_bytes)
print(f"Subscribing to invoice (hash={INVOICE_HASH_HEX}) via gRPC...")
updates = invoices_stub.SubscribeSingleInvoice(request)
for invoice_update in updates:
    print(invoice_update)  # invoice_update is an Invoice message
print("Invoice subscription stream ended.")
```

```php
// Subscribe to a single invoice's state changes via gRPC
$invoiceHashHex = getenv('INVOICE_HASH_HEX') ?: '<invoice_hash_in_hex>';
$invoiceClient = new InvoicesClient($grpcHost, $opts);

// Prepare request with the invoice RHash (as raw bytes)
$invoiceReq = new Invoicesrpc\SubscribeSingleInvoiceRequest();
$invoiceReq->setRHash(hex2bin($invoiceHashHex));

// Initiate the RPC stream
$invoiceStream = $invoiceClient->SubscribeSingleInvoice($invoiceReq);
echo "Subscribed to invoice {$invoiceHashHex} (single invoice stream).\n";

// Read streamed Invoice updates
while (true) {
    /** @var Lnrpc\Invoice $invoiceUpdate */
    $invoiceUpdate = $invoiceStream->read();
    if ($invoiceUpdate === null) {
        // Stream ended (invoice reached a final state and stream closed)
        echo "Invoice stream closed by server.\n";
        break;
    }
    // Log the invoice's current state and amount paid
    $state = $invoiceUpdate->getState();  // e.g., 0=OPEN, 1=SETTLED, 2=CANCELED
    $settledFlag = $invoiceUpdate->getSettled();  // boolean
    echo "🔔 Invoice update: state=" . ($settledFlag ? 'SETTLED' : 'OPEN') 
         . ", amount_paid_sat=" . $invoiceUpdate->getAmtPaidSat() . PHP_EOL;
    if ($settledFlag || $state === \Lnrpc\Invoice\InvoiceState::CANCELED) {
        // Stop if invoice is settled or canceled
        echo "Invoice is now final (settled or canceled). Ending subscription.\n";
        break;
    }
}
```
:::

## Subscribe to All Invoices

Monitor all invoice activity on your node, with the ability to resume from a specific point.

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

:::CodeblockTabs
```javascript
// grpc-all-invoices.js - Subscribe to all invoice updates via gRPC
const fs = require("fs");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

// --- Configuration ---
const LND_HOST = process.env.LND_HOST;
const MACAROON_PATH = process.env.MACAROON_PATH;
const PROTO_PATH = process.env.PROTO_PATH || "./lightning.proto";

// Resume points (0 = start from beginning)
const ADD_INDEX = process.env.ADD_INDEX || "0";
const SETTLE_INDEX = process.env.SETTLE_INDEX || "0";

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

const loaderOptions = {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};

const packageDefinition = protoLoader.loadSync(PROTO_PATH, loaderOptions);

async function subscribeToInvoices() {
  process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA";

  // Set up credentials
  const macaroonBytes = fs.readFileSync(MACAROON_PATH);
  const macaroonHex = macaroonBytes.toString("hex");
  
  const metadata = new grpc.Metadata();
  metadata.add("macaroon", macaroonHex);
  
  const macaroonCreds = grpc.credentials.createFromMetadataGenerator(
    (_args, callback) => callback(null, metadata)
  );
  const sslCreds = grpc.credentials.createSsl();
  const credentials = grpc.credentials.combineChannelCredentials(
    sslCreds,
    macaroonCreds
  );

  // Create client
  const lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
  const lnrpc = lnrpcDescriptor.lnrpc;
  const client = new lnrpc.Lightning(`${LND_HOST}:10009`, credentials);

  console.log(`📡 Subscribing to invoices (add_index: ${ADD_INDEX}, settle_index: ${SETTLE_INDEX})...`);

  // Subscribe with resume points
  const call = client.subscribeInvoices({
    add_index: ADD_INDEX,
    settle_index: SETTLE_INDEX,
  });

  // Track invoice counts
  let invoiceCount = 0;
  let settledCount = 0;

  call.on("data", (invoice) => {
    invoiceCount++;
    
    console.log(`\n📦 Invoice #${invoiceCount}:`);
    console.log(`  • Hash:         ${Buffer.from(invoice.r_hash).toString("base64")}`);
    console.log(`  • State:        ${invoice.state}`);
    console.log(`  • Value:        ${invoice.value} sats`);
    console.log(`  • Amount Paid:  ${invoice.amt_paid_sat} sats`);
    console.log(`  • Add Index:    ${invoice.add_index}`);
    
    if (invoice.settled) {
      settledCount++;
      console.log(`  • Settle Index: ${invoice.settle_index}`);
      console.log(`  • ✅ SETTLED (Total settled: ${settledCount})`);
    }
  });

  call.on("error", (err) => {
    console.error("❌ Stream error:", err.message);
  });

  call.on("end", () => {
    console.log("🔌 Invoice stream ended.");
  });

  // Graceful shutdown
  process.on("SIGINT", () => {
    console.log("\n⏹️  Shutting down...");
    call.cancel();
    process.exit(0);
  });
}

subscribeToInvoices().catch(console.error);
```

```python
import os, codecs, grpc
# Generated classes from lightning.proto (lnrpc)
import lightning_pb2 as lnrpc
import lightning_pb2_grpc as lightningstub

GRPC_HOST = os.getenv("LND_GRPC_HOST", "localhost:10009")
TLS_CERT_PATH = os.getenv("LND_TLS_CERT", "tls.cert")
MACAROON_PATH = os.getenv("LND_MACAROON_PATH")
if not MACAROON_PATH:
    raise EnvironmentError("Please set LND_MACAROON_PATH env variable.")

# Set up credentials (TLS and macaroon) for gRPC
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
cert = open(TLS_CERT_PATH, "rb").read()
ssl_creds = grpc.ssl_channel_credentials(cert)
macaroon_bytes = open(MACAROON_PATH, "rb").read()
macaroon_hex = codecs.encode(macaroon_bytes, "hex")
def metadata_callback(context, callback):
    callback([("macaroon", macaroon_hex)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
channel = grpc.secure_channel(GRPC_HOST, combined_creds)

lightning_stub = lightningstub.LightningStub(channel)

# Subscribe to all invoice events (no indexes specified for new events only)
request = lnrpc.InvoiceSubscription()  # leaving add_index and settle_index unset
print("Subscribing to all invoices via gRPC...")
for invoice in lightning_stub.SubscribeInvoices(request):
    # Each 'invoice' is an Invoice message (new or updated)
    print(invoice)
```

```php
// Subscribe to all invoice add/settle events via gRPC
$lightningClient = new LightningClient($grpcHost, $opts);
$subReq = new Lnrpc\InvoiceSubscription();  // empty request = subscribe from latest
// (Optionally, set add_index and/or settle_index on $subReq to resume from a specific point)

$invoiceStream = $lightningClient->SubscribeInvoices($subReq);
echo "Subscribed to all invoice events.\n";

// Stream all invoice updates continuously
while (true) {
    $invoice = $invoiceStream->read();
    if ($invoice === null) {
        echo "Invoice event stream closed.\n";
        break;
    }
    $hashHex = bin2hex($invoice->getRHash()); 
    $stateStr = $invoice->getSettled() ? 'settled' : ($invoice->getState() === \Lnrpc\Invoice\InvoiceState::CANCELED ? 'canceled' : 'open');
    echo "📣 Invoice #{$invoice->getAddIndex()} ({$hashHex}): {$stateStr}, value={$invoice->getValueSat()} sat\n";
    // (Process invoice data as needed; this loop continues indefinitely)
}
```
:::

## Track Single Payment

Monitor the progress of a specific outgoing payment.

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
// grpc-track-payment.js - Track a single payment via gRPC
const fs = require("fs");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

// --- Configuration ---
const LND_HOST = process.env.LND_HOST;
const MACAROON_PATH = process.env.MACAROON_PATH;
const PROTO_PATH = process.env.PROTO_PATH || "./lightning.proto";
const PAYMENT_HASH = process.env.PAYMENT_HASH; // hex or base64

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

const loaderOptions = {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};

const packageDefinition = protoLoader.loadSync(PROTO_PATH, loaderOptions);

async function trackPayment() {
  process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA";

  // Set up credentials
  const macaroonBytes = fs.readFileSync(MACAROON_PATH);
  const macaroonHex = macaroonBytes.toString("hex");
  
  const metadata = new grpc.Metadata();
  metadata.add("macaroon", macaroonHex);
  
  const macaroonCreds = grpc.credentials.createFromMetadataGenerator(
    (_args, callback) => callback(null, metadata)
  );
  const sslCreds = grpc.credentials.createSsl();
  const credentials = grpc.credentials.combineChannelCredentials(
    sslCreds,
    macaroonCreds
  );

  // Create router client
  const lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
  const routerrpc = lnrpcDescriptor.routerrpc;
  const client = new routerrpc.Router(`${LND_HOST}:10009`, credentials);

  console.log(`📡 Tracking payment ${PAYMENT_HASH}...`);

  // Convert payment hash to Buffer
  let paymentHashBuffer;
  if (PAYMENT_HASH.length === 64) {
    // Hex format
    paymentHashBuffer = Buffer.from(PAYMENT_HASH, "hex");
  } else {
    // Base64 format
    paymentHashBuffer = Buffer.from(PAYMENT_HASH, "base64");
  }

  // Track payment
  const call = client.trackPaymentV2({
    payment_hash: paymentHashBuffer,
    no_inflight_updates: false, // Get all updates, not just final
  });

  let attemptCount = 0;

  call.on("data", (payment) => {
    attemptCount++;
    
    console.log(`\n💸 Payment update #${attemptCount}:`);
    console.log(`  • Status:       ${payment.status}`);
    console.log(`  • Value:        ${payment.value_sat} sats`);
    console.log(`  • Fee:          ${payment.fee_sat} sats`);
    console.log(`  • HTLCs:        ${payment.htlcs?.length || 0}`);
    
    if (payment.failure_reason) {
      console.log(`  • Failure:      ${payment.failure_reason}`);
    }

    // Log HTLC attempts
    if (payment.htlcs && payment.htlcs.length > 0) {
      console.log("  • Attempts:");
      payment.htlcs.forEach((htlc, idx) => {
        console.log(`    ${idx + 1}. Status: ${htlc.status}, Route: ${htlc.route?.hops?.length || 0} hops`);
      });
    }

    if (payment.status === "SUCCEEDED") {
      console.log("✅ Payment successful!");
      call.cancel();
    } else if (payment.status === "FAILED") {
      console.log("❌ Payment failed!");
      call.cancel();
    }
  });

  call.on("error", (err) => {
    if (err.code !== grpc.status.CANCELLED) {
      console.error("❌ Stream error:", err.message);
    }
  });

  call.on("end", () => {
    console.log("🔌 Payment tracking ended.");
  });
}

trackPayment().catch(console.error);
```

```python
import os, codecs, grpc
# Generated classes from router.proto (routerrpc)
import router_pb2 as routerrpc
import router_pb2_grpc as routerstub

GRPC_HOST = os.getenv("LND_GRPC_HOST", "localhost:10009")
TLS_CERT_PATH = os.getenv("LND_TLS_CERT", "tls.cert")
MACAROON_PATH = os.getenv("LND_MACAROON_PATH")
PAYMENT_HASH_HEX = os.getenv("LND_PAYMENT_HASH")
if not (MACAROON_PATH and PAYMENT_HASH_HEX):
    raise EnvironmentError("Please set LND_MACAROON_PATH and LND_PAYMENT_HASH env variables.")

# Set up secure channel with credentials
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
cert = open(TLS_CERT_PATH, "rb").read()
ssl_creds = grpc.ssl_channel_credentials(cert)
macaroon_bytes = open(MACAROON_PATH, "rb").read()
macaroon_hex = codecs.encode(macaroon_bytes, "hex")
def metadata_callback(context, callback):
    callback([("macaroon", macaroon_hex)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
channel = grpc.secure_channel(GRPC_HOST, combined_creds)

router_stub = routerstub.RouterStub(channel)

# Track a single outgoing payment by its hash
payment_hash_bytes = bytes.fromhex(PAYMENT_HASH_HEX)
request = routerrpc.TrackPaymentRequest(payment_hash=payment_hash_bytes)
print(f"Tracking payment {PAYMENT_HASH_HEX} via gRPC...")
for payment_update in router_stub.TrackPaymentV2(request):
    print(payment_update)
print("Payment tracking stream ended.")
```

```php
// Track a single payment's progress via gRPC
$paymentHashHex = getenv('PAYMENT_HASH_HEX') ?: '<payment_hash_in_hex>';
$routerClient = new RouterClient($grpcHost, $opts);

$trackReq = new Routerrpc\TrackPaymentRequest();
$trackReq->setPaymentHash(hex2bin($paymentHashHex));
$trackReq->setNoInflightUpdates(false);  // set true to only get final update

$paymentStream = $routerClient->TrackPaymentV2($trackReq);
echo "Tracking payment {$paymentHashHex} (gRPC)...\n";

// Read payment status updates
while (true) {
    /** @var Lnrpc\Payment $payment */
    $payment = $paymentStream->read();
    if ($payment === null) {
        echo "Payment stream closed by server.\n";
        break;
    }
    $status = $payment->getStatus();  // an enum of type PaymentStatus
    // Map status to human-readable form:
    $statusName = ['UNKNOWN', 'IN_FLIGHT', 'SUCCEEDED', 'FAILED'][$status] ?? $status;
    echo "💱 Payment update: status={$statusName}";
    if ($statusName === 'SUCCEEDED') {
        $preimage = bin2hex($payment->getPaymentPreimage());
        echo ", preimage={$preimage}\n";
        echo "✅ Payment succeeded, amount_sent={$payment->getValueSat()} sat, total_fee={$payment->getFeeSat()} sat\n";
        break;
    } elseif ($statusName === 'FAILED') {
        echo "\n❌ Payment failed (failure reason: {$payment->getFailureReason()}).\n";
        break;
    } else {
        echo " (still in flight)\n";
        // continue loop for more intermediate updates (e.g. HTLC attempts)
    }
}
```
:::

## Track All Payments

Monitor all outgoing payment activity on your node in real-time.

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

:::CodeblockTabs
```javascript
// grpc-track-all-payments.js - Track all payments via gRPC streaming
const fs = require("fs");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

// --- Configuration ---
const LND_HOST = process.env.LND_HOST;
const MACAROON_PATH = process.env.MACAROON_PATH;
const PROTO_PATH = process.env.PROTO_PATH || "./lightning.proto";
const NO_INFLIGHT = process.env.NO_INFLIGHT === "true"; // Show only final states

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

const loaderOptions = {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};

const packageDefinition = protoLoader.loadSync(PROTO_PATH, loaderOptions);

async function trackAllPayments() {
  process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA";

  // Set up credentials
  const macaroonBytes = fs.readFileSync(MACAROON_PATH);
  const macaroonHex = macaroonBytes.toString("hex");
  
  const metadata = new grpc.Metadata();
  metadata.add("macaroon", macaroonHex);
  
  const macaroonCreds = grpc.credentials.createFromMetadataGenerator(
    (_args, callback) => callback(null, metadata)
  );
  const sslCreds = grpc.credentials.createSsl();
  const credentials = grpc.credentials.combineChannelCredentials(
    sslCreds,
    macaroonCreds
  );

  // Create router client
  const lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
  const routerrpc = lnrpcDescriptor.routerrpc;
  const client = new routerrpc.Router(`${LND_HOST}:10009`, credentials);

  console.log(`📡 Tracking all payments (no_inflight_updates: ${NO_INFLIGHT})...`);

  // Track all payments
  const call = client.trackPayments({
    no_inflight_updates: NO_INFLIGHT,
  });

  // Track statistics
  const stats = {
    total: 0,
    inFlight: 0,
    succeeded: 0,
    failed: 0,
    totalFees: 0,
    totalSent: 0,
  };

  call.on("data", (payment) => {
    stats.total++;
    
    console.log(`\n💸 Payment event #${stats.total}:`);
    console.log(`  • Hash:         ${Buffer.from(payment.payment_hash).toString("hex").substring(0, 16)}...`);
    console.log(`  • Status:       ${payment.status}`);
    console.log(`  • Value:        ${payment.value_sat} sats`);
    console.log(`  • Fee:          ${payment.fee_sat} sats`);
    console.log(`  • Created:      ${new Date(parseInt(payment.creation_time_ns) / 1000000).toISOString()}`);

    // Update statistics
    switch (payment.status) {
      case "IN_FLIGHT":
        stats.inFlight++;
        break;
      case "SUCCEEDED":
        stats.succeeded++;
        stats.totalFees += parseInt(payment.fee_sat || 0);
        stats.totalSent += parseInt(payment.value_sat || 0);
        console.log(`  • ✅ Success! Total succeeded: ${stats.succeeded}`);
        break;
      case "FAILED":
        stats.failed++;
        console.log(`  • ❌ Failed! Reason: ${payment.failure_reason}`);
        break;
    }

    // Show running totals
    console.log(`\n📊 Statistics:`);
    console.log(`  • In Flight:    ${stats.inFlight}`);
    console.log(`  • Succeeded:    ${stats.succeeded}`);
    console.log(`  • Failed:       ${stats.failed}`);
    console.log(`  • Total Sent:   ${stats.totalSent} sats`);
    console.log(`  • Total Fees:   ${stats.totalFees} sats`);
  });

  call.on("error", (err) => {
    console.error("❌ Stream error:", err.message);
  });

  call.on("end", () => {
    console.log("🔌 Payment stream ended.");
  });

  // Graceful shutdown
  process.on("SIGINT", () => {
    console.log("\n⏹️  Shutting down...");
    console.log("Final statistics:", stats);
    call.cancel();
    process.exit(0);
  });
}

trackAllPayments().catch(console.error);
```

```python
import os, codecs, grpc
import router_pb2 as routerrpc
import router_pb2_grpc as routerstub

GRPC_HOST = os.getenv("LND_GRPC_HOST", "localhost:10009")
TLS_CERT_PATH = os.getenv("LND_TLS_CERT", "tls.cert")
MACAROON_PATH = os.getenv("LND_MACAROON_PATH")
if not MACAROON_PATH:
    raise EnvironmentError("Please set LND_MACAROON_PATH env variable.")

# Setup gRPC channel credentials (TLS + macaroon)
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
cert = open(TLS_CERT_PATH, "rb").read()
ssl_creds = grpc.ssl_channel_credentials(cert)
macaroon_bytes = open(MACAROON_PATH, "rb").read()
macaroon_hex = codecs.encode(macaroon_bytes, "hex")
def metadata_callback(context, callback):
    callback([("macaroon", macaroon_hex)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
channel = grpc.secure_channel(GRPC_HOST, combined_creds)

router_stub = routerstub.RouterStub(channel)

# Subscribe to updates for all payments (ongoing and new)
request = routerrpc.TrackPaymentsRequest(no_inflight_updates=False)
print("Subscribing to all outgoing payments via gRPC...")
for payment in router_stub.TrackPayments(request):
    print(payment)
```

```php
// Subscribe to updates for all in-flight payments via gRPC
$routerClient = new RouterClient($grpcHost, $opts);
$streamReq = new Routerrpc\TrackPaymentsRequest();
$streamReq->setNoInflightUpdates(false);  // set true to only get final outcomes

$paymentsStream = $routerClient->TrackPayments($streamReq);
echo "Subscribed to all payments stream.\n";

while (true) {
    /** @var Lnrpc\Payment $payment */
    $payment = $paymentsStream->read();
    if ($payment === null) {
        echo "Payments stream closed (no more data).\n";
        break;
    }
    $hashHex = $payment->getPaymentHash();
    $status = $payment->getStatus();
    $statusName = ['UNKNOWN','IN_FLIGHT','SUCCEEDED','FAILED'][$status] ?? $status;
    echo "📤 Payment {$payment->getPaymentIndex()} (hash={$hashHex}): {$statusName}";
    if ($statusName === 'SUCCEEDED') {
        echo " (sent {$payment->getValueSat()} sat, fee {$payment->getFeeSat()} sat)";
    } elseif ($statusName === 'FAILED') {
        echo " (failed: reason={$payment->getFailureReason()})";
    }
    echo PHP_EOL;
    // (The loop continues to stream updates for all ongoing/new payments)
}
```
:::

