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:
GET https://yourwallet.com/.well-known/lnurlp/aliceand parses the response.- Validates
commentAllowed,minSendable,maxSendable(see LNURL-pay setup). If any check fails, the recipient is skipped silently for that campaign. AcommentAllowedabove zero but shorter than the message does not skip the recipient. The service cuts the message to fit instead. GET <callback>?amount=<msats>&comment=<urlencoded>and parses the returned BOLT-11.- 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 campaign message, without the"📨 Satogram: "prefix - 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>:
{
"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:
{
"r_hash": "20b1c195...",
"value": 15,
"settled": true,
"is_keysend": true,
"amt_paid_sat": 15,
"htlcs": [
{
"amt_msat": 15000,
"custom_records": {
"34349334": "676d2066726f6d20746162636f6e6621",
"5482373484": "<32-byte preimage>",
"6789998212": "616c6963654079...,626f624079...,6361726f6c4079..."
}
}
]
}Decode hex on 34349334 and you get "📨 Satogram: gm from tabconf!" for Path A and "gm from tabconf!" for Path B. Decode 6789998212 for the recipient list.
What your code needs to do
The same four things in every implementation:
- Subscribe to settled incoming payments on your node.
- Extract TLV
34349334(message) and TLV6789998212(recipient or comma-separated recipients) from the final-hop HTLC. - Split TLV
6789998212on the comma character. For Path A this gives one address; for Path B, up to 50. - Credit each recipient with their share of
amount_msat(minus your delivery fee on Path B keysends only), 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_satogramsats per recipient (1 sat by default, configurable per campaign). YourminSendableon 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): a batch has to be worth at least 10 sats in total (
recipients * amt_per_satogram). A batch worth less is not sent as a keysend at all, its recipients are paid over LNURL-pay instead. Each recipient’s share isamt_per_satogram, not 10 sats. If your wallet has a credit floor above that share, 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 | bLIP-3 keysend | Keysend preimage |
6789998212 | Satogram (this project) | Recipient identifier(s) for custodial credit lookup |