bLIP: XX
Title: BOLT 12 Merchant Cashback Vouchers
Status: Draft
Author: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Created: 2026-06-07
License: CC0
This bLIP defines a protocol for merchants to grant returning customers a
cashback voucher (e.g., 10% of the paid amount in "virtual sats") when they pay
in bitcoin over Lightning using BOLT 12. The customer is identified across
payments using the bLIP 42 contact mechanism (invreq_contact_secret). The
voucher balance is held in the merchant's ledger (e.g., the BitVault Pay
backend) and is redeemable only at the issuing merchant, as a discount on a
future payment. No funds move at accrual time: the cashback is funded by
deducting it from the customer's next payment.
This proposal composes with bLIP 42 (payer identification) and bLIP 56 (PoS payment notifications), but only bLIP 42 support is required.
This bLIP is licensed under the CC0 license.
A merchant (e.g., a wine shop such as MrVino.ch) wants to incentivize bitcoin payments by offering a loyalty discount: pay in bitcoin, get 10% back as a voucher for your next purchase at the same shop. This is attractive for the merchant (lower fees than cards, repeat customers) and for the customer (effective discount), and it gives Lightning wallets a UX comparable to fiat loyalty programs.
Today there is no interoperable way to do this on Lightning:
- The merchant cannot recognize a returning customer. bLIP 42 solves this with
invreq_contact_secret, but it is specified for contacts between wallets, not for merchant loyalty. - There is no way for the merchant to communicate an available voucher balance to the customer's wallet, nor for the wallet to choose between accumulating and redeeming it.
This bLIP specifies both, using only existing BOLT 12 message flows
(invoice_request / invoice) plus new odd TLV fields, so unaware wallets
continue to work unchanged.
- Customer: BOLT 12 wallet supporting bLIP 42 contact fields and the TLVs defined here. Unsupporting wallets can still pay normally; they simply never accrue or redeem vouchers.
- Merchant: Always-online Lightning node (or service such as BitVault Pay acting for the merchant) that maintains a voucher ledger: a private database mapping a customer identifier to a voucher balance in msat.
- Point-of-Sale (optional): A bLIP 56 PoS terminal. It is not on the payment path and does not participate in voucher logic; it only displays the order and payment confirmation. When a voucher is redeemed, the discounted amount is what the PoS must expect (see Interaction with bLIP 56).
The expected deployment is that a payment provider (e.g., BitVault Pay) provides the merchant with both the bitcoin PoS terminal implementing bLIP 56 and the voucher ledger, and operates (or hosts) the merchant's Lightning node. The merchant's only integration step is installing the PoS. In this configuration the provider sees both sides — it constructs the per-order offers at the PoS and processes the notifications at the merchant node — so it can also surface the voucher balance and the accumulate/redeem choice directly on the PoS screen, in addition to the customer's wallet. Nothing in this bLIP requires this co-location: the protocol works identically when the PoS, the merchant node, and the ledger are run by independent parties.
The customer identifier for the voucher ledger is the bLIP 42
invreq_contact_secret included by the customer in the invoice_request.
Requirements:
- The customer's wallet MUST obtain explicit user consent before including identifying fields, since loyalty tracking is a deliberate privacy trade-off ("join the loyalty program" action in the wallet).
- The wallet MUST reuse the same
contact_secretfor all payments to the same merchant offer, derived deterministically as in bLIP 42, so the balance survives wallet restore from seed. - The merchant MUST treat the
contact_secretas an opaque ledger key. It MUST NOT requireinvreq_payer_offerorinvreq_payer_bip_353_namefor voucher accrual (the merchant never needs to pay the customer back).
A new feature bit is defined for offer_features:
| Bit | Name | Description |
|---|---|---|
| TBD | cashback_vouchers |
Merchant supports this voucher protocol |
The merchant SHOULD set this (odd) bit in its offers. A wallet seeing this bit
knows it can include a contact_secret to participate and that the invoice
may carry voucher TLVs.
All types are odd (ignorable by unaware implementations) and TBD, to be reserved in bLIP 2.
| Type | Name | Description |
|---|---|---|
| TBD | voucher_balance_msat |
u64. Customer's redeemable balance at this merchant. |
| TBD | voucher_rate |
u16. Cashback rate for this payment, in basis points (1000 = 10%). |
| TBD | voucher_applied_msat |
u64. Amount of voucher applied as a discount to this invoice. |
| Type | Name | Description |
|---|---|---|
| TBD | voucher_redeem_msat |
u64. Amount of voucher balance the customer asks to apply to this payment. |
- The customer scans the merchant's offer (directly or via a bLIP 56 PoS QR)
and sends an
invoice_requestincludinginvreq_contact_secret(bLIP 42). - The merchant responds with an
invoicefor the full amount. If thecontact_secretis known, the invoice includesvoucher_balance_msat(the current balance) andvoucher_rate. For a newcontact_secret, balance is 0. - The customer pays the invoice.
- On claiming the payment, the merchant credits the ledger:
balance[contact_secret] += paid_amount_msat * voucher_rate / 10_000.
The accrual is computed on the amount actually paid in this payment, net of any voucher discount applied (no cashback on the cashback).
The wallet learns the available balance from voucher_balance_msat in the
invoice of step 2 and prompts the user: accumulate or redeem.
- Accumulate: the customer simply pays the invoice. Flow ends as above.
- Redeem: the customer sends a new
invoice_requestfor the same offer, now includingvoucher_redeem_msat. The merchant:- Verifies
voucher_redeem_msat <= balance[contact_secret]. - Issues a new
invoicewithinvoice_amount = order_amount - voucher_applied_msat, wherevoucher_applied_msat = min(voucher_redeem_msat, order_amount), and includesvoucher_applied_msatand the post-redemptionvoucher_balance_msat. - On payment claim, debits the ledger by
voucher_applied_msatand credits the new accrual on the discounted amount paid.
- Verifies
If the redeeming payment is never completed, the ledger MUST NOT be debited: the debit happens only when the discounted invoice is claimed.
Customer Merchant (+ ledger)
| |
| 1. invoice_request |
| (invreq_contact_secret) ----------> | lookup balance
| |
| 2. <--------------------- invoice |
| (amount, voucher_balance_msat, |
| voucher_rate) |
| |
| -- user chooses: redeem -- |
| |
| 3. invoice_request |
| (invreq_contact_secret, |
| voucher_redeem_msat) ------------> | check balance
| |
| 4. <--------------------- invoice |
| (amount - applied, |
| voucher_applied_msat, |
| voucher_balance_msat) |
| |
| 5. payment --------------------------> | debit applied,
| | credit accrual
The two-round-trip redemption is intentional: the balance is only known after the first invoice, and the user decision must happen before the discounted invoice is issued. Both round trips are onion messages and complete in seconds at a PoS.
The merchant:
- MUST key the ledger by
invreq_contact_secretper offer. - MUST only credit accrual when the payment is claimed (preimage released).
- MUST only debit redemption when the discounted invoice is claimed.
- SHOULD include
voucher_balance_msatandvoucher_ratein every invoice issued to a knowncontact_secret. - MUST ignore
voucher_redeem_msatexceeding the balance (or issue an undiscounted invoice), never issue a negative-amount invoice. - MAY expire balances; expiry policy is out of protocol scope but SHOULD be disclosed to the customer out of band.
The customer's wallet:
- MUST NOT include
voucher_redeem_msatwithout an explicit user choice. - SHOULD display the balance, the applied discount, and the new balance.
- MUST treat voucher TLVs as informational from an untrusted party: the ledger is custodial trust in the merchant (see Trust Model).
When the merchant order is presented through a bLIP 56 PoS:
- The PoS builds the per-order offer for the full order amount as usual.
- If the customer redeems a voucher, the merchant's
payment_notificationwill carryamount_msatlower than the order amount the PoS expects. To avoid a spuriousnotification_nack, the merchant SHOULD include the newvoucher_applied_msatTLV in thepayment_notification, and the PoS MUST accept a payment whereamount_msat + voucher_applied_msat >= expected_amount. - The PoS SHOULD display the discount on the confirmation screen and record it for accounting (the discount is a merchant rebate, relevant for taxes).
| Type | Name | Description |
|---|---|---|
| TBD | voucher_applied_msat |
u64. Voucher discount applied to this order. |
The voucher balance is a custodial IOU from the merchant, comparable to a paper loyalty card. The customer cannot prove the balance and the merchant can refuse redemption. This is acceptable because:
- The amounts are small (a percentage of past purchases).
- The merchant's incentive is repeat business; defrauding loyalty customers is self-defeating.
- No funds are locked: the customer never pre-pays for the voucher; it is funded entirely as a discount on a future payment.
A merchant-signed accrual receipt (e.g., signing
contact_secret || balance || timestamp) would let customers prove balances
and is listed as a possible extension in Open Questions.
- Participating links all of a customer's payments at one merchant. This is the entire point of a loyalty program, but it MUST be opt-in per merchant.
- The
contact_secretis scoped per merchant offer (per bLIP 42 derivation), so merchants cannot correlate a customer across different merchants. - A customer can leave the program at any time by simply omitting the contact fields (forfeiting the balance).
Leaked contact_secret: anyone who learns the secret can redeem the
victim's balance (they cannot steal funds, only the discount). Wallets MUST
treat contact secrets as sensitive data. Merchants MAY additionally require
the bLIP 42 invreq_payer_bip_353_signature (or a payer-key signature over
the invoice_request) before honoring redemption above a threshold; this is
left optional to keep the base flow working for minimal wallets.
Double redemption race: two concurrent redeeming invoice_requests against
the same balance MUST NOT result in over-redemption; the merchant debits at
claim time and MUST cap total in-flight voucher_applied_msat at the balance.
Amount games at a PoS: as in bLIP 56, the customer could alter the offer
amount; the PoS validation rule above (amount + voucher_applied >= expected)
keeps post-payment validation intact.
Issuing real sats back per payment (streaming cashback) costs fees and liquidity per purchase and leaks the customer's receiving offer to the merchant. An ecash or token-based voucher would be bearer-redeemable but adds a mint and a whole trust framework. A ledger keyed by the existing bLIP 42 identifier needs zero new cryptography, zero locked funds, and matches how loyalty programs already work. The voucher never leaves the issuing merchant, so a custodial ledger is the natural fit.
No money moves at accrual time, so the merchant carries no payable and the customer takes no custodial deposit risk beyond an expected discount. It also makes the incentive structurally a retention tool: the value only materializes if the customer returns.
contact_secret already solves deterministic, seed-restorable, per-peer
mutual identification, and wallets are starting to implement it. Reusing it
means a wallet with bLIP 42 support only needs the two voucher TLVs and a UX
prompt to participate.
The invoice is the only merchant→customer message in the base BOLT 12 flow that exists before payment. Carrying the balance there lets the wallet make the accumulate/redeem decision with no extra protocol messages and no merchant web API.
All new TLVs use odd types: wallets and nodes that do not understand them
ignore them and pay normally (they just never accrue a balance the merchant
advertises... the merchant MAY still accrue for any invoice_request carrying
a contact_secret, so a bLIP 42-only wallet accrues silently and discovers
the balance once upgraded). bLIP 56 PoS terminals without the
voucher_applied_msat extension will nack discounted payments, so merchants
SHOULD NOT enable redemption on orders flowing through a non-upgraded PoS.
TODO: BitVault Pay backend (merchant ledger + invoice TLVs), the BitVault-provided bLIP 56 PoS terminal, and a wallet prototype. Pilot deployment planned with MrVino.ch.
- Idea validation: to be proposed on Delving Bitcoin / lightning-dev for feedback before requesting a bLIP number and bLIP 2 TLV reservations.
- Provable balances: should the merchant return a signed accrual receipt so customers can prove their balance (and merchants can be audited)?
- Redemption authentication: should redemption above some amount REQUIRE
a payer signature instead of bare
contact_secretpossession? - Voucher expiry: in-protocol expiry signaling (e.g.,
voucher_expiryTLV) vs out-of-band terms. - Fiat-denominated balances: the ledger is msat-denominated; merchants
pricing in fiat (CHF) may prefer fiat-denominated vouchers. A currency
TLV mirroring
offer_currencycould be added. - Regulatory: whether a merchant-scoped, non-transferable discount balance constitutes e-money in target jurisdictions (CH) is out of scope here but must be validated before deployment.
Builds on bLIP 42 (Bastien Teinturier) and the bLIP 56 PoS notification design (Vincenzo Palazzo, Jeffrey Czyz).