UMA Invoice and Request
An UMA invoice is a data structure that describes payments that can be coordinated using UMA and
provides a way for a payment sender to have proof of payment–without having to use the existing
LNURLPRequest/Response handshake flow.
UMA invoices allow a payment recipient to specify an amount they want to receive in any currency,
along with some details on how to pay. This is useful in many cases where the recipient is the
initiator of a payment. For example, merchant checkout flows, a bill from any vendor or service
provider, etc.
UMA requests are a way to deliver UMA invoices to sending wallets through methods like
deep/universal links on mobile, notifications, etc.
The invoice format looks like this:
@dataclass
class Invoice(TLVCodable):
# Receiving UMA address
receiver_uma: str
# Invoice UUID Served as both the identifier of the UMA invoice, and the validation of proof of payment.
invoice_uuid: str
# The amount of invoice to be paid in the smallest unit of the ReceivingCurrency.
amount: int
# The currency of the invoice
receiving_currency: InvoiceCurrency
# The unix timestamp the UMA invoice expires
expiration: int
# Indicates whether the VASP is a financial institution that requires travel rule information.
is_subject_to_travel_rule: bool
# RequiredPayerData the data about the payer that the sending VASP must provide in order to send a payment.
required_payer_data: Optional[InvoiceCounterpartyDataOptions]
# UmaVersions is a list of UMA versions that the VASP supports for this transaction. It should be
# containing the highest minor version of each major version it supported, separated by commas.
uma_versions: str
# CommentCharsAllowed is the number of characters that the sender can include in the comment field of the pay request.
comment_chars_allowed: Optional[int]
# The sender's UMA address. If this field presents, the UMA invoice should directly go to the sending VASP instead of showing in other formats.
sender_uma: Optional[str]
# The maximum number of times the invoice can be paid
max_num_payments: Optional[int]
# KYC status of the receiver, default is verified.
kyc_status: Optional[KycStatus]
# The callback url that the sender should send the PayRequest to.
callback: str
# The signature of the UMA invoice
signature: Optional[bytes]
UMA SDK provides an easy way to create an UMA invoice. Most of the fields are similar to the fields
in LNURLPResponse:
invoice = create_uma_invoice(
receiver_uma="$bob@vasp2.com",
receiving_currency_amount=amount,
receiving_currency=InvoiceCurrency(
code=currency.code,
name=currency.name,
symbol=currency.symbol,
decimals=currency.decimals,
),
expiration=two_days_from_now,
callback=callback,
is_subject_to_travel_rule=True,
signing_private_key=signing_private_key,
required_payer_data=payer_data_options,
receiver_kyc_status=KycStatus.VERIFIED,
)
Once the invoice object is created, the receiver can encode it to a bech32 string:
invoice_string = invoice.to_bech32_string()
The invoice string can be displayed in QR code, or as a deep link:
uma: <invoice_string>
Then it's up to the sending wallet to implement this url scheme for deep linking to redirect to the
wallet app.
When the sending wallet receives an UMA invoice string, it should decode it to an UMA invoice object and
validate the signature:
invoice = Invoice.from_bech32_string(invoice_string)
verify_uma_invoice_signature(invoice, receiver_vasp_pubkey)
When decoding the invoice string, the SDK will automatically validate that all the required fields
are present.
After this step, the sending VASP should validate that the invoice has not expired. It should
then proceed directly to the PayRequest step of the protocol. All information needed should be included
in the invoice object.
For a sending VASP that supports UMA requests, a request endpoint should be defined in the api
configuration:
"uma_request_endpoint": "https://vasp1.com/path/to/request/url"
This must be a POST endpoint with a body payload of the invoice string.
When requesting a payment from a specific sender, the receiving VASP should create the invoice with
the sender field filled:
When requesting a payment from a specific sender, the receiving VASP should create the invoice with the
sender field filled:
invoice = create_uma_invoice(
receiver_uma="$bob@vasp2.com",
receiving_currency_amount=amount,
receiving_currency=InvoiceCurrency(
code=currency.code,
name=currency.name,
symbol=currency.symbol,
decimals=currency.decimals,
),
expiration=two_days_from_now,
callback=callback,
is_subject_to_travel_rule=True,
signing_private_key=signing_private_key,
required_payer_data=payer_data_options,
receiver_kyc_status=KycStatus.VERIFIED,
sender_uma="$alice@vasp1.com"
)
Then the receiving VASP should query the sending VASP's configuration to get the request endpoint to
send the invoice to sending VASP:
vars = {"invoice": invoice_str}
res = requests.post(
url,
json=vars,
timeout=20,
)
Once the sending VASP receives the request, it should validate the invoice and then send a push
notification to the sender's wallet app to let the sender confirm before sending the payment.