Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Receiving payments using LNURL-Pay and Lightning addresses

What is a Lightning address?

A Lightning address is a human-readable identifier formatted like an email address (e.g., user@domain.com) that can be used to receive Bitcoin payments over the Lightning Network. Behind the scenes, it uses the LNURL-Pay protocol to dynamically generate invoices when someone wants to send a payment to this address.

Configuring a custom domain

To use Lightning addresses with the Breez SDK, you first need to supply a domain. There are two options:

  1. Use a hosted LNURL server: You can have your custom domain configured to an LNURL server run by Breez.
  2. Self-hosted LNURL server: You can run your own LNURL server in a self-hosted environment.

In case you choose to point your domain to a hosted LNURL server, you will need to add a CNAME record in your domain's DNS settings.

Note:: If you're using Cloudflare, make sure the CNAME record is set to 'DNS only' (not 'Proxied').

Option 1: Using your domain without any subdomain

This points yourdomain.com directly to the LNURL server. Some DNS providers do not support this method. If yours doesn't support CNAME or ALIAS records for the root domain, you will need to configure your domain at the registrar level to use an external DNS provider (like Google Cloud DNS).

  • Host/Name: @
  • Type: CNAME (or ALIAS if available)
  • Value/Target: breez.tips

Option 2: Using a subdomain This points a subdomain like pay.yourdomain.com to the LNURL server.

  • Host/Name: pay (or your chosen prefix like payment, tip, donate)
  • Type: CNAME
  • Value/Target: breez.tips

Send us your domain name (e.g., yourdomain.com or pay.yourdomain.com).

We will verify and add it to our list of allowed domains.

Configuring Lightning addresses for users

Configure your domain in the SDK by passing the lnurl_domain parameter in the SDK configuration:

Rust
let mut config = default_config(Network::Mainnet);
config.api_key = Some("your-api-key".to_string());
config.lnurl_domain = Some("yourdomain.com".to_string());
Swift
var config = defaultConfig(network: Network.mainnet)
config.apiKey = "your-api-key"
config.lnurlDomain = "yourdomain.com"
Kotlin
val config = defaultConfig(Network.MAINNET)
config.apiKey = "your-api-key"
config.lnurlDomain = "yourdomain.com"
C#
var config = BreezSdkSparkMethods.DefaultConfig(Network.Mainnet) with
{
    apiKey = "your-api-key",
    lnurlDomain = "yourdomain.com"
};
Javascript
const config = defaultConfig('mainnet')
config.apiKey = 'your-api-key'
config.lnurlDomain = 'yourdomain.com'
React Native
const config = defaultConfig(Network.Mainnet)
config.apiKey = 'your-api-key'
config.lnurlDomain = 'yourdomain.com'
Flutter
final config = defaultConfig(network: Network.mainnet)
    .copyWith(
      apiKey: 'your-api-key',
      lnurlDomain: 'yourdomain.com'
    );
Python
config = default_config(network=Network.MAINNET)
config.api_key = "your-api-key"
config.lnurl_domain = "yourdomain.com"
Go
lnurlDomain := "yourdomain.com"
apiKey := "your-api-key"
config := breez_sdk_spark.DefaultConfig(breez_sdk_spark.NetworkMainnet)
config.ApiKey = &apiKey
config.LnurlDomain = &lnurlDomain

Managing Lightning addresses API docs

The SDK provides several functions to manage Lightning addresses:

Checking address availability

Before registering a Lightning address, you can check if the username is available. In your UI you can use a quick check mark to show the address is available before registering.

Rust
let request = CheckLightningAddressRequest { username };

let is_available = sdk.check_lightning_address_available(request).await?;
Swift
let request = CheckLightningAddressRequest(
    username: username
)

let available = try await sdk.checkLightningAddressAvailable(req: request)
Kotlin
val request = CheckLightningAddressRequest(
    username = username
)

val available = sdk.checkLightningAddressAvailable(request)
C#
var request = new CheckLightningAddressRequest(username: username);
var isAvailable = await sdk.CheckLightningAddressAvailable(request);
Javascript
const request = {
  username
}

const available = await sdk.checkLightningAddressAvailable(request)
React Native
const request = {
  username
}

const available = await sdk.checkLightningAddressAvailable(request)
Flutter
final request = CheckLightningAddressRequest(
  username: username,
);

final available = await sdk.checkLightningAddressAvailable(request: request);
Python
request = CheckLightningAddressRequest(username=username)
is_available = await sdk.check_lightning_address_available(request)
Go
request := breez_sdk_spark.CheckLightningAddressRequest{
	Username: username,
}

isAvailable, err := sdk.CheckLightningAddressAvailable(request)
if err != nil {
	return false, err
}

Registering a Lightning address

Once you've confirmed a username is available, you can register it by passing a username and a description. The username will be used in username@domain.com. The description will be included in lnurl metadata and as the invoice description, so this is what the sender will see. The description is optional, and will default to Pay to username@domain.com.

Rust
let request = RegisterLightningAddressRequest {
    username,
    description,
};

let address_info = sdk.register_lightning_address(request).await?;
let lightning_address = address_info.lightning_address;
let lnurl = address_info.lnurl;
Swift
let request = RegisterLightningAddressRequest(
    username: username,
    description: description
)

let addressInfo = try await sdk.registerLightningAddress(request: request)
let lightningAddress = addressInfo.lightningAddress
let lnurl = addressInfo.lnurl
Kotlin
val request = RegisterLightningAddressRequest(
    username = username,
    description = description
)

val addressInfo = sdk.registerLightningAddress(request)
val lightningAddress = addressInfo.lightningAddress
val lnurl = addressInfo.lnurl
C#
var request = new RegisterLightningAddressRequest(
    username: username,
    description: description
);

var addressInfo = await sdk.RegisterLightningAddress(request);
var lightningAddress = addressInfo.lightningAddress;
var lnurl = addressInfo.lnurl;
Javascript
const request = {
  username,
  description
}

const addressInfo = await sdk.registerLightningAddress(request)
const lightningAddress = addressInfo.lightningAddress
const lnurl = addressInfo.lnurl
React Native
const request = {
  username,
  description
}

const addressInfo = await sdk.registerLightningAddress(request)
const lightningAddress = addressInfo.lightningAddress
const lnurl = addressInfo.lnurl
Flutter
final request = RegisterLightningAddressRequest(
  username: username,
  description: description,
);

final addressInfo = await sdk.registerLightningAddress(request: request);
final lightningAddress = addressInfo.lightningAddress;
final lnurl = addressInfo.lnurl;
Python
request = RegisterLightningAddressRequest(
    username=username,
    description=description
)

address_info = await sdk.register_lightning_address(request)
lightning_address = address_info.lightning_address
lnurl = address_info.lnurl
Go
request := breez_sdk_spark.RegisterLightningAddressRequest{
	Username:    username,
	Description: &description,
}

addressInfo, err := sdk.RegisterLightningAddress(request)
if err != nil {
	return nil, err
}

_ = addressInfo.LightningAddress
_ = addressInfo.Lnurl

Retrieving Lightning address information

You can retrieve information about the currently registered Lightning address.

Rust
let address_info_opt = sdk.get_lightning_address().await?;

if let Some(info) = address_info_opt {
    let lightning_address = &info.lightning_address;
    let username = &info.username;
    let description = &info.description;
    let lnurl = &info.lnurl;
}
Swift
if let addressInfo = try await sdk.getLightningAddress() {
    let lightningAddress = addressInfo.lightningAddress
    let username = addressInfo.username
    let description = addressInfo.description
    let lnurl = addressInfo.lnurl
}
Kotlin
val addressInfoOpt = sdk.getLightningAddress()

if (addressInfoOpt != null) {
    val lightningAddress = addressInfoOpt.lightningAddress
    val username = addressInfoOpt.username
    val description = addressInfoOpt.description
    val lnurl = addressInfoOpt.lnurl
}
C#
var addressInfoOpt = await sdk.GetLightningAddress();

if (addressInfoOpt != null)
{
    var lightningAddress = addressInfoOpt.lightningAddress;
    var username = addressInfoOpt.username;
    var description = addressInfoOpt.description;
    var lnurl = addressInfoOpt.lnurl;
}
Javascript
const addressInfoOpt = await sdk.getLightningAddress()

if (addressInfoOpt != null) {
  const lightningAddress = addressInfoOpt.lightningAddress
  const username = addressInfoOpt.username
  const description = addressInfoOpt.description
  const lnurl = addressInfoOpt.lnurl
}
React Native
const addressInfoOpt = await sdk.getLightningAddress()

if (addressInfoOpt != null) {
  const lightningAddress = addressInfoOpt.lightningAddress
  const username = addressInfoOpt.username
  const description = addressInfoOpt.description
  const lnurl = addressInfoOpt.lnurl
}
Flutter
final addressInfoOpt = await sdk.getLightningAddress();

if (addressInfoOpt == null) {
  throw Exception("No Lightning Address registered for this user.");
}

final lightningAddress = addressInfoOpt.lightningAddress;
final username = addressInfoOpt.username;
final description = addressInfoOpt.description;
final lnurl = addressInfoOpt.lnurl;
Python
address_info_opt = await sdk.get_lightning_address()

if address_info_opt is not None:
    lightning_address = address_info_opt.lightning_address
    username = address_info_opt.username
    description = address_info_opt.description
    lnurl = address_info_opt.lnurl
Go
addressInfoOpt, err := sdk.GetLightningAddress()
if err != nil {
	return nil, err
}

if addressInfoOpt != nil {
	_ = addressInfoOpt.LightningAddress
	_ = addressInfoOpt.Username
	_ = addressInfoOpt.Description
	_ = addressInfoOpt.Lnurl
}

Deleting a Lightning address

When a user no longer wants to use the Lightning address, you can delete it.

Rust
sdk.delete_lightning_address().await?;
Swift
try await sdk.deleteLightningAddress()
Kotlin
sdk.deleteLightningAddress()
C#
await sdk.DeleteLightningAddress();
Javascript
await sdk.deleteLightningAddress()
React Native
await sdk.deleteLightningAddress()
Flutter
await sdk.deleteLightningAddress();
Python
await sdk.delete_lightning_address()
Go
err := sdk.DeleteLightningAddress()
if err != nil {
	return err
}

Accessing LNURL payment metadata

When receiving payments via LNURL-Pay or Lightning addresses, additional metadata may be included with the payment. This metadata is available on the received payment.

Sender comment

If the sender includes a comment with their payment (as defined in LUD-12), it will be available on the received payment. This is the message that the sender wrote when making the payment.

Rust
// Check if this is a lightning payment with LNURL receive metadata
if let Some(PaymentDetails::Lightning {
    lnurl_receive_metadata: Some(metadata),
    ..
}) = payment.details
{
    // Access the sender comment if present
    if let Some(comment) = metadata.sender_comment {
        println!("Sender comment: {}", comment);
    }
}
Swift
// Check if this is a lightning payment with LNURL receive metadata
if case .lightning(let details) = payment.details {
    // Access the sender comment if present
    if let metadata = details.lnurlReceiveMetadata,
       let comment = metadata.senderComment {
        print("Sender comment: \(comment)")
    }
}
Kotlin
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details is PaymentDetails.Lightning) {
    val details = payment.details as PaymentDetails.Lightning
    val metadata = details.lnurlReceiveMetadata

    // Access the sender comment if present
    metadata?.senderComment?.let { comment ->
        println("Sender comment: $comment")
    }
}
C#
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details is PaymentDetails.Lightning lightningDetails)
{
    var metadata = lightningDetails.lnurlReceiveMetadata;

    // Access the sender comment if present
    if (metadata?.senderComment != null)
    {
        Console.WriteLine($"Sender comment: {metadata.senderComment}");
    }
}
Javascript
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details?.type === 'lightning') {
  const metadata = payment.details.lnurlReceiveMetadata

  // Access the sender comment if present
  if (metadata?.senderComment != null) {
    console.log('Sender comment:', metadata.senderComment)
  }
}
React Native
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details?.tag === PaymentDetails_Tags.Lightning) {
  const metadata = payment.details.inner.lnurlReceiveMetadata

  // Access the sender comment if present
  if (metadata?.senderComment != null) {
    console.log('Sender comment:', metadata.senderComment)
  }
}
Flutter
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details case PaymentDetails_Lightning lightningDetails) {
  final metadata = lightningDetails.lnurlReceiveMetadata;

  // Access the sender comment if present
  final comment = metadata?.senderComment;
  if (comment != null) {
    print('Sender comment: $comment');
  }
}
Python
# Check if this is a lightning payment with LNURL receive metadata
if isinstance(payment.details, PaymentDetails.LIGHTNING):
    metadata = payment.details.lnurl_receive_metadata

    # Access the sender comment if present
    if metadata is not None and metadata.sender_comment is not None:
        print(f"Sender comment: {metadata.sender_comment}")
Go
// Check if this is a lightning payment with LNURL receive metadata
if lightningDetails, ok := (*payment.Details).(breez_sdk_spark.PaymentDetailsLightning); ok {
	metadata := lightningDetails.LnurlReceiveMetadata

	// Access the sender comment if present
	if metadata != nil && metadata.SenderComment != nil {
		println("Sender comment:", *metadata.SenderComment)
	}
}

Nostr Zap request

If the payment was sent as a Nostr Zap (as defined in NIP-57), the received payment will include the zap request event. It carries the signed Nostr event (kind 9734) used to create the zap, and will also include the zap receipt event (kind 9735) once that has been created and published.

Rust
// Check if this is a lightning payment with LNURL receive metadata
if let Some(PaymentDetails::Lightning {
    lnurl_receive_metadata: Some(metadata),
    ..
}) = payment.details
{
    // Access the Nostr zap request if present
    if let Some(zap_request) = metadata.nostr_zap_request {
        // The zap_request is a JSON string containing the Nostr event (kind 9734)
        println!("Nostr zap request: {}", zap_request);
    }

    // Access the Nostr zap receipt if present
    if let Some(zap_receipt) = metadata.nostr_zap_receipt {
        // The zap_receipt is a JSON string containing the Nostr event (kind 9735)
        println!("Nostr zap receipt: {}", zap_receipt);
    }
}
Swift
// Check if this is a lightning payment with LNURL receive metadata
if case .lightning(let details) = payment.details {
    if let metadata = details.lnurlReceiveMetadata {
        // Access the Nostr zap request if present
        if let zapRequest = metadata.nostrZapRequest {
            // The zapRequest is a JSON string containing the Nostr event (kind 9734)
            print("Nostr zap request: \(zapRequest)")
        }

        // Access the Nostr zap receipt if present
        if let zapReceipt = metadata.nostrZapReceipt {
            // The zapReceipt is a JSON string containing the Nostr event (kind 9735)
            print("Nostr zap receipt: \(zapReceipt)")
        }
    }
}
Kotlin
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details is PaymentDetails.Lightning) {
    val details = payment.details as PaymentDetails.Lightning
    val metadata = details.lnurlReceiveMetadata

    // Access the Nostr zap request if present
    metadata?.nostrZapRequest?.let { zapRequest ->
        // The zapRequest is a JSON string containing the Nostr event (kind 9734)
        println("Nostr zap request: $zapRequest")
    }

    // Access the Nostr zap receipt if present
    metadata?.nostrZapReceipt?.let { zapReceipt ->
        // The zapReceipt is a JSON string containing the Nostr event (kind 9735)
        println("Nostr zap receipt: $zapReceipt")
    }
}
C#
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details is PaymentDetails.Lightning lightningDetails)
{
    var metadata = lightningDetails.lnurlReceiveMetadata;

    if (metadata != null)
    {
        // Access the Nostr zap request if present
        if (metadata.nostrZapRequest != null)
        {
            // The nostrZapRequest is a JSON string containing the Nostr event (kind 9734)
            Console.WriteLine($"Nostr zap request: {metadata.nostrZapRequest}");
        }

        // Access the Nostr zap receipt if present
        if (metadata.nostrZapReceipt != null)
        {
            // The nostrZapReceipt is a JSON string containing the Nostr event (kind 9735)
            Console.WriteLine($"Nostr zap receipt: {metadata.nostrZapReceipt}");
        }
    }
}
Javascript
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details?.type === 'lightning') {
  const metadata = payment.details.lnurlReceiveMetadata

  // Access the Nostr zap request if present
  if (metadata?.nostrZapRequest != null) {
    // The nostrZapRequest is a JSON string containing the Nostr event (kind 9734)
    console.log('Nostr zap request:', metadata.nostrZapRequest)
  }

  // Access the Nostr zap receipt if present
  if (metadata?.nostrZapReceipt != null) {
    // The nostrZapReceipt is a JSON string containing the Nostr event (kind 9735)
    console.log('Nostr zap receipt:', metadata.nostrZapReceipt)
  }
}
React Native
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details?.tag === PaymentDetails_Tags.Lightning) {
  const metadata = payment.details.inner.lnurlReceiveMetadata

  // Access the Nostr zap request if present
  if (metadata?.nostrZapRequest != null) {
    // The nostrZapRequest is a JSON string containing the Nostr event (kind 9734)
    console.log('Nostr zap request:', metadata.nostrZapRequest)
  }

  // Access the Nostr zap receipt if present
  if (metadata?.nostrZapReceipt != null) {
    // The nostrZapReceipt is a JSON string containing the Nostr event (kind 9735)
    console.log('Nostr zap receipt:', metadata.nostrZapReceipt)
  }
}
Flutter
// Check if this is a lightning payment with LNURL receive metadata
if (payment.details case PaymentDetails_Lightning lightningDetails) {
  final metadata = lightningDetails.lnurlReceiveMetadata;

  if (metadata != null) {
    // Access the Nostr zap request if present
    final zapRequest = metadata.nostrZapRequest;
    if (zapRequest != null) {
      // The zapRequest is a JSON string containing the Nostr event (kind 9734)
      print('Nostr zap request: $zapRequest');
    }

    // Access the Nostr zap receipt if present
    final zapReceipt = metadata.nostrZapReceipt;
    if (zapReceipt != null) {
      // The zapReceipt is a JSON string containing the Nostr event (kind 9735)
      print('Nostr zap receipt: $zapReceipt');
    }
  }
}
Python
# Check if this is a lightning payment with LNURL receive metadata
if isinstance(payment.details, PaymentDetails.LIGHTNING):
    metadata = payment.details.lnurl_receive_metadata

    if metadata is not None:
        # Access the Nostr zap request if present
        if metadata.nostr_zap_request is not None:
            # The nostr_zap_request is a JSON string containing the Nostr event (kind 9734)
            print(f"Nostr zap request: {metadata.nostr_zap_request}")

        # Access the Nostr zap receipt if present
        if metadata.nostr_zap_receipt is not None:
            # The nostr_zap_receipt is a JSON string containing the Nostr event (kind 9735)
            print(f"Nostr zap receipt: {metadata.nostr_zap_receipt}")
Go
// Check if this is a lightning payment with LNURL receive metadata
if lightningDetails, ok := (*payment.Details).(breez_sdk_spark.PaymentDetailsLightning); ok {
	metadata := lightningDetails.LnurlReceiveMetadata

	if metadata != nil {
		// Access the Nostr zap request if present
		if metadata.NostrZapRequest != nil {
			// The NostrZapRequest is a JSON string containing the Nostr event (kind 9734)
			println("Nostr zap request:", *metadata.NostrZapRequest)
		}

		// Access the Nostr zap receipt if present
		if metadata.NostrZapReceipt != nil {
			// The NostrZapReceipt is a JSON string containing the Nostr event (kind 9735)
			println("Nostr zap receipt:", *metadata.NostrZapReceipt)
		}
	}
}

Note: When used in private mode, the nostr zap receipt will be published by the SDK when online. When used in public mode, the zap receipt will be published by the LNURL server on your behalf.