Invoice
How to create, send, parse, and manage invoices in the Fiber network
TL;DR
- Create an invoice:
fnn-cli invoice new_invoice --amount 1000 --currency fibt - Pay an invoice:
fnn-cli payment send_payment --invoice "fibt1..." - Check invoice status:
fnn-cli invoice get_invoice --payment-hash 0x... - Invoices use bech32m encoding with network-specific prefixes (
fibb,fibt,fibd) - Invoice states:
Open→Received→Paid, or →Cancelled/Expired
An invoice is a payment request generated by a recipient in the Fiber network. It encodes everything a sender needs to make a payment — the amount, the recipient's identity, cryptographic proof of payment, and optional metadata such as a description or expiry time.
Think of an invoice as a "payment QR code." The recipient generates one, and the sender scans it (or copies the encoded string) to initiate a payment through send_payment.
Anatomy of an Invoice
Every Fiber invoice carries the following core fields:
| Field | Required | Description |
|---|---|---|
| Currency | Yes | Network identifier: fibb (mainnet), fibt (testnet), or fibd (devnet) |
| Amount | No | Payment amount in shannons (1 CKB = 10⁸ shannons). Omit for open-ended invoices (e.g., donations) |
| Payment hash | Yes | A 32-byte cryptographic commitment. For regular invoices it's derived from a random preimage; for hold invoices, the recipient provides the preimage and the hash is computed from it |
| Timestamp | Yes | Invoice creation time in milliseconds since the UNIX epoch |
| Signature | Yes | A secp256k1 recoverable signature over the invoice data, proving the invoice was created by the holder of the payee's private key |
In addition, invoices can carry optional attributes:
| Attribute | Description |
|---|---|
| Description | A human-readable note, up to 639 characters (e.g., "Coffee") |
| Expiry time | How long the invoice remains valid, in seconds from the timestamp |
| Payee public key | The recipient's compressed secp256k1 public key (33 bytes). Enables signature verification without external lookup |
| Fallback address | A CKB on-chain address to fall back to if the off-chain payment fails |
| UDT type script | A CKB type script identifying a User Defined Token, for non-CKB asset payments |
| Final expiry delta | Minimum TLC (Time-Locked Contract) expiry delta for the final hop, in milliseconds (min ~160 minutes, max 14 days) |
| Hash algorithm | Hash function used for the payment hash: ckb_hash (blake2b-256, default) or sha256 |
| Feature flags | Capability flags: multi-part payments (MPP), trampoline routing |
| Payment secret | A 32-byte secret required for multi-part payments; auto-generated when MPP is enabled |
Invoice Format
Fiber invoices use bech32m encoding — the same checksum scheme used by modern Bitcoin addresses, but with a different data layout (not compatible with Lightning's BOLT 11). The design draws inspiration from BOLT 11 while adapting to the CKB ecosystem: invoice data is serialized using molecule (the standard serialization format in CKB projects), and cross-chain compatibility is handled through Fiber's built-in Cross-Chain Hub rather than through the invoice format itself.
Human-Readable Part (HRP)
The HRP combines a network prefix with an optional amount:
| Prefix | Network |
|---|---|
fibb | CKB mainnet ("fiber bytes", since 1 CKB = 1 Byte) |
fibt | CKB testnet |
fibd | CKB devnet |
The amount is appended directly as a decimal number in shannons. For example, fibb1280 means mainnet, 1280 shannons. If no amount is specified, the HRP is just the prefix (e.g., fibb), indicating an open-ended invoice.
Data Part
The data part is encoded as base32 (u5) values and contains:
- A flag byte indicating whether the invoice is signed
- The invoice data, serialized and compressed (see below)
- The signature (if signed): 104 u5 values encoding a 65-byte secp256k1 recoverable signature
The maximum decompressed invoice data size is 16 KB.
Data Fields
The serialized invoice data contains the following fields:
| # | Field | Required | Size | Description |
|---|---|---|---|---|
| 1 | timestamp | Yes | 128 bits | Milliseconds since the UNIX epoch |
| 2 | payment_hash | Yes | 256 bits | Unique identifier of the invoice. Derived from blake2b_256(preimage) for hold invoices, or randomly generated for AMP invoices |
| 3 | expiry | No | 64 bits | Validity period in seconds; timestamp + expiry gives the expiration time |
| 4 | description | No | Variable | UTF-8 text (e.g., "a cup of coffee"), max 639 characters |
| 5 | final_expiry_delta | No | 64 bits | Final TLC expiry delta in milliseconds |
| 6 | fallback_address | No | Variable | CKB on-chain address for fallback if payment fails |
| 7 | feature | No | Variable | Feature flags (MPP, trampoline routing, etc.) |
| 8 | payee_public_key | No | 33 bytes | Compressed secp256k1 public key of the payee |
| 9 | udt_script | No | Variable | CKB type script for UDT token payments |
| 10 | hash_algorithm | No | 1 byte | 0 = ckb_hash (blake2b-256, default), 1 = sha256 |
| 11 | payment_secret | No | 32 bytes | Secret required for multi-part payments |
Encoding Pipeline
Raw molecule-serialized bytes tend to contain consecutive zeros when optional fields are empty, making the bech32m output relatively long. To address this, Fiber compresses the molecule bytes using arithmetic coding (lossless), which roughly halves the encoded length:
data = arcode_compress(molecule_bytes(invoice_data)) ++ signature
encode(hrp, data, Variant::Bech32m)Decoding performs the inverse: bech32m decode, split signature from data, then arithmetic decompression, then molecule deserialization.
Signature
The signature is a 65-byte secp256k1 recoverable signature ([u8; 65] = 520 bits = 104 u5 values). It proves the invoice was generated by the holder of the payee's private key and can be used to verify integrity and correctness.
The signing process:
message_hash = SHA256(hrp_bytes ++ base32_decode(data_without_signature))
signature = Secp256k1::sign_ecdsa_recoverable(message_hash, &private_key)The signature is appended after the compressed data before the final bech32m encoding. An unsigned invoice (flag byte = 0) omits the signature entirely.
Example
fibt1000...encoded_data...checksumCreating an Invoice
Using fnn-cli
Create a basic invoice with a specified amount:
fnn-cli invoice new_invoice \
--amount 1000 \
--currency fibt \
--description "Coffee"The amount is specified in shannons (1 CKB = 10⁸ shannons). The currency must match your node's network configuration.
To create an invoice with an explicit expiry:
fnn-cli invoice new_invoice \
--amount 500000000 \
--currency fibt \
--description "Monthly subscription" \
--expiry 86400The --expiry value is in seconds (86400 = 24 hours).
Using RPC
{
"jsonrpc": "2.0",
"method": "new_invoice",
"params": [
{
"amount": "0x3E8",
"currency": "Fibt",
"description": "Coffee",
"expiry": "0xE10"
}
],
"id": 1
}All numeric fields in the RPC are hex-encoded. The amount is in shannons (0x3E8 = 1000), and expiry is in seconds (0xE10 = 3600, i.e. 1 hour).
Full parameter reference:
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | u128 (hex) | Yes | Invoice amount in shannons |
currency | Currency | Yes | Fibb (mainnet), Fibt (testnet), or Fibd (devnet) |
description | string | No | Human-readable description (max 639 chars) |
payment_preimage | Hash256 | No | Settlement preimage. Mutually exclusive with payment_hash. If both are omitted, a random preimage is generated |
payment_hash | Hash256 | No | Payment hash only (creates a hold invoice). Mutually exclusive with payment_preimage |
expiry | u64 (hex) | No | Invoice validity period in seconds |
fallback_address | string | No | CKB on-chain fallback address |
final_expiry_delta | u64 (hex) | No | Final TLC expiry delta in milliseconds (min ~160 minutes, max 14 days) |
udt_type_script | Script | No | CKB type script for UDT (non-CKB) assets |
hash_algorithm | HashAlgorithm | No | ckb_hash (default) or sha256 |
allow_mpp | bool | No | Enable multi-part payments |
allow_trampoline_routing | bool | No | Enable trampoline routing |
Response:
{
"invoice_address": "fibt1000...encoded_invoice...",
"invoice": {
"currency": "Fibt",
"amount": "0x3E8",
"signature": "0x...",
"data": {
"timestamp": "0x...",
"payment_hash": "0x...",
"attrs": [...]
}
}
}The invoice_address is the bech32m-encoded string you share with the sender. The invoice object contains the full parsed invoice.
Paying an Invoice
Once you have an encoded invoice string, you can pay it using send_payment.
Using fnn-cli
fnn-cli payment send_payment --invoice "fibt1..."Using RPC
{
"jsonrpc": "2.0",
"method": "send_payment",
"params": [
{
"invoice": "fibt1..."
}
],
"id": 1
}The payment module will validate the invoice, check for expiry, find a route through the network, and lock funds in TLCs along the path. For details on what happens after the payment is initiated, see Payment Lifecycle.
Parsing an Invoice
Before paying, you may want to inspect an invoice's contents — for example, to verify the amount or check the recipient's public key.
Using fnn-cli
fnn-cli invoice parse_invoice --invoice "fibt1..."Using RPC
{
"jsonrpc": "2.0",
"method": "parse_invoice",
"params": ["fibt1..."],
"id": 1
}This returns the full CkbInvoice object without paying it. Both signed and unsigned invoices can be parsed.
Invoice States
An invoice progresses through the following states:
| State | Description |
|---|---|
| Open | The invoice has been created and is waiting to be paid |
| Received | A TLC matching the invoice's payment hash has arrived at the recipient's node, but the invoice has not yet been settled. This is the typical intermediate state for hold invoices |
| Paid | The preimage has been revealed and the payment is fully settled. The recipient has received the funds |
| Cancelled | The invoice was manually cancelled by the recipient via cancel_invoice |
| Expired | The invoice has passed its expiry time (timestamp + expiry). This state is detected automatically |
| Transition | Trigger |
|---|---|
| Open → Received | A TLC matching the invoice's payment hash arrives at the recipient's node |
| Received → Paid | The preimage is revealed (via settle_invoice or automatic settlement) |
| Received → Cancelled | The recipient calls cancel_invoice — held TLCs are rejected |
| Received → Expired | Hold timeout fires before settle or cancel — held TLCs are released |
| Open → Cancelled | The recipient calls cancel_invoice before any payment arrives |
| Open → Expired | Current time exceeds timestamp + expiry (detected automatically) |
For regular invoices, the status changes from Open directly to Paid almost instantly — skipping the Received state entirely. The Received state is typically only observable for hold invoices, where the TLC is held until the recipient explicitly calls settle_invoice.
Checking Invoice Status
Retrieve the current state of an invoice using its payment hash.
Using fnn-cli
fnn-cli invoice get_invoice --payment-hash 0x...Using RPC
{
"jsonrpc": "2.0",
"method": "get_invoice",
"params": ["0x..."],
"id": 1
}The response includes the invoice address, the full invoice object, and the current status. If the invoice has passed its expiry time, the status is automatically reported as Expired even if it was previously Open.
Cancelling an Invoice
You can cancel an invoice that has not yet been paid. This is useful when a payment is no longer expected or when a hold invoice's condition was not met.
Using fnn-cli
fnn-cli invoice cancel_invoice --payment-hash 0x...Using RPC
{
"jsonrpc": "2.0",
"method": "cancel_invoice",
"params": ["0x..."],
"id": 1
}You cannot cancel an invoice that has already been paid (Paid) or is already Cancelled. Cancelling a Received invoice will release any held TLCs back to the sender.
Settling a Hold Invoice
For hold invoices, settlement is a manual step. After the recipient has verified that the payment condition is met, they call settle_invoice with the preimage.
{
"jsonrpc": "2.0",
"method": "settle_invoice",
"params": [
{
"payment_hash": "0x...",
"payment_preimage": "0x..."
}
],
"id": 1
}The node validates that hash_algorithm(preimage) == payment_hash, stores the preimage, and fulfills all held TLCs for this payment hash across all channels. Only invoices in the Received state can be settled.
Always store the preimage securely when creating a hold invoice. If you lose the preimage, the funds remain locked until the invoice expires or the TLC times out.
For the full hold invoice workflow — including creation, settlement, cancellation, and cross-chain swap usage — see Hold Invoice.
User Defined Tokens (UDT)
Fiber invoices support payments in assets other than native CKB by including a UDT type script — a CKB type script that identifies the token on-chain.
To create a UDT invoice, pass the udt_type_script parameter:
{
"jsonrpc": "2.0",
"method": "new_invoice",
"params": [
{
"amount": "0x2710",
"currency": "Fibt",
"description": "10000 units of MyToken",
"udt_type_script": {
"code_hash": "0x...",
"hash_type": "type",
"args": "0x..."
}
}
],
"id": 1
}When a UDT type script is present, the amount refers to the UDT's base units rather than shannons. The sender's node must have a channel that supports the specified UDT to complete the payment.
Advanced Features
Multi-Path Payments (MPP)
When allow_mpp is set to true, the invoice signals that it supports receiving payments split across multiple routes. This is useful for large payments that exceed the capacity of any single channel path.
fnn-cli invoice new_invoice \
--amount 10000000 \
--currency fibt \
--allow-mpp trueWhen MPP is enabled, a payment_secret is automatically generated and included in the invoice. The sender's payment module uses this secret to coordinate the partial payments. See Payment Lifecycle for details on how multi-path routing works, and Multi-Hop Payments for how payments traverse intermediate nodes.
Trampoline Routing
When allow_trampoline_routing is set to true, the invoice indicates the recipient supports trampoline routing — a routing optimization where the sender delegates part of the route-finding to intermediate "trampoline" nodes. See Trampoline Routing for a detailed explanation.
fnn-cli invoice new_invoice \
--amount 5000 \
--currency fibt \
--allow-trampoline-routing trueHash Algorithm
By default, Fiber uses blake2b-256 (ckb_hash) for payment hashes — the same hash function used throughout CKB. For cross-chain interoperability with networks that use SHA-256 (such as the Bitcoin Lightning Network), you can specify sha256:
fnn-cli invoice new_invoice \
--amount 1000 \
--currency fibt \
--hash-algorithm sha256This is particularly relevant for cross-chain atomic swaps where both sides of the swap must use the same hash algorithm.
Quick Reference
| Action | RPC Method | fnn-cli |
|---|---|---|
| Create invoice | new_invoice | fnn-cli invoice new_invoice |
| Parse invoice | parse_invoice | fnn-cli invoice parse_invoice |
| Get invoice status | get_invoice | fnn-cli invoice get_invoice |
| Cancel invoice | cancel_invoice | fnn-cli invoice cancel_invoice |
| Settle hold invoice | settle_invoice | fnn-cli invoice settle_invoice |
| Pay an invoice | send_payment | fnn-cli payment send_payment |
Related Topics
- Hold Invoice — deferred settlement for conditional payments and atomic swaps
- Multi-Hop Payments — how payments are routed through intermediate nodes
- Trampoline Routing — delegating pathfinding to trampoline nodes for lightweight clients
- Payment Lifecycle — how payments are routed and settled after an invoice is paid
- Cross-Chain HTLC — how hold invoices enable cross-chain swaps between Fiber and Lightning