How It Works#

This doc explains the data model and protocol. The next docs cover wiring it up.

The two delivery paths#

The Satogram service routes a payment to a custodial user through one of two paths.

Path A: per-recipient LNURL-pay (default)#

For every lightning address the service knows about, it runs the standard LNURL-pay flow:

  1. GET https://yourwallet.com/.well-known/lnurlp/alice and parses the response.
  2. Validates commentAllowed, minSendable, maxSendable (see LNURL-pay setup). If any check fails, the recipient is skipped silently for that campaign.
  3. GET <callback>?amount=<msats>&comment=<urlencoded> and parses the returned BOLT-11.
  4. Validates the invoice’s msat amount equals what was requested. Then pays it via SendPaymentV2.

When the service pays the BOLT-11, it attaches two TLVs as destination custom records so they land in the recipient’s HTLC alongside the payment:

TLV Contents
34349334 "📨 Satogram: " + <user message> (UTF-8 bytes)
6789998212 The recipient’s lightning address, e.g. alice@yourwallet.com

This is why your subscriber sees both TLVs even though the invoice itself is a normal BOLT-11. The Lightning final-hop onion carries them through.

Path B: batch keysend (opt-in)#

If your wallet’s domain is on the operator’s opt-in list (your domain mapped to your node’s pubkey), the service bundles up to 50 recipients on your domain into a single keysend to your node:

  • Keysend value_msat = num_recipients × amt_per_satogram × 1000
  • TLV 34349334 = the message (same "📨 Satogram: …" prefix as above)
  • TLV 6789998212 = comma-separated list of recipient addresses, e.g. alice@yourwallet.com,bob@yourwallet.com,carol@yourwallet.com
  • TLV 5482373484 = keysend preimage (your LND validates this and only accepts the HTLC if it hashes to the payment hash)

Batches are flushed when they hit 50 entries or ~800 bytes of address payload, whichever comes first.

Batching saves on routing fees and reduces the number of inbound HTLCs your node has to settle. It’s also the path on which you can take a custodial cut, see Why integrate and Batch keysend opt-in.

What you’ll see on the wire#

A settled LNURL-pay Satogram (Path A) looks like this from lncli lookupinvoice <r_hash>:

JSON
{
  "memo": "alice@yourwallet.com",
  "r_hash": "20b1c195...",
  "value": 5,
  "value_msat": 5000,
  "settled": true,
  "is_keysend": false,
  "amt_paid_sat": 5,
  "htlcs": [
    {
      "amt_msat": 5000,
      "custom_records": {
        "34349334":   "f09f93a8205361746f6772616d3a20676d2066726f6d20746162636f6e6621",
        "6789998212": "616c69636540796f757277616c6c65742e636f6d"
      }
    }
  ]
}

A batched keysend Satogram (Path B) for 3 recipients at 5 sats each:

JSON
{
  "r_hash": "20b1c195...",
  "value": 15,
  "settled": true,
  "is_keysend": true,
  "amt_paid_sat": 15,
  "htlcs": [
    {
      "amt_msat": 15000,
      "custom_records": {
        "34349334":   "f09f93a8205361746f6772616d3a20676d2066726f6d20746162636f6e6621",
        "5482373484": "<32-byte preimage>",
        "6789998212": "616c6963654079...,626f624079...,6361726f6c4079..."
      }
    }
  ]
}

Decode hex on 34349334 and you get "📨 Satogram: gm from tabconf!". Decode 6789998212 for the recipient list.

What your code needs to do#

The same four things in every implementation:

  1. Subscribe to settled incoming payments on your node.
  2. Extract TLV 34349334 (message) and TLV 6789998212 (recipient or comma-separated recipients) from the final-hop HTLC.
  3. Split TLV 6789998212 on the comma character. For Path A this gives one address; for Path B, up to 50.
  4. Credit each recipient with their share of amount_msat (minus your custodial cut if you’ve opted into Path B), idempotent on (payment_hash, recipient).

What changes between implementations is how you get at the TLVs. The next docs cover the LNURL-pay callback (which generates the invoice) and the subscriber (which reads the TLVs on settlement).

Minimums and amounts#

Two minimums matter:

  • LNURL-pay (Path A): the service sends amt_per_satogram sats per recipient (typically 5 sats, but configurable per campaign). Your minSendable on the LNURL-pay endpoint is the floor, set it low (1000 msat / 1 sat) to ensure you qualify for every campaign.
  • Batch keysend (Path B): the service enforces a minimum of 10 sats per recipient when paying lightning-address-style recipients in batches. Below that the batch rounds up. If your wallet has a credit floor higher than 10 sats, you’ll need a policy for dust, see Batch keysend opt-in.

TLV registry#

TLV Origin Meaning
34349334 satoshis.stream TLV registry Keysend message (UTF-8)
5482373484 BOLT-04 / LND keysend Keysend preimage
6789998212 Satogram (this project) Recipient identifier(s) for custodial credit lookup

Reference examples#

  • Working reference subscriber (LND, Go): examples/lnd/main.go