my2sats
← Back to posts
Cashu Makes Bitcoin Payments Feel Native to the Web

Cashu Makes Bitcoin Payments Feel Native to the Web

egge··Updated October 27, 2026
CashuPaymentDevelopment

Cashu Payment Requests: Linking Payments to Resources on the Web

Bitcoin is a natural fit for payments on the web. It provides a global monetary layer that anyone can use, without requiring the sender and receiver to share a bank or payment provider.

But building a good payment experience still takes work. A web application needs to connect each incoming payment to something meaningful: a user account, an order, a subscription, or a particular resource. With on-chain Bitcoin or Lightning, that often means creating a payment identifier, tracking its state, waiting for settlement, and connecting the result back to the application.

Cashu payment requests offer another way to approach this problem. They describe what the receiver wants to be paid and, when a transport is specified, how the payment should be delivered. That delivery target can carry context the application already understands.

For example, an HTTP transport can point to a URL containing an account or order ID. When the payment arrives, the URL itself tells the receiver what the payment is for.

What is a Cashu payment request?

A Cashu payment request gives a wallet the information it needs to construct and deliver an ecash payment. It can specify an amount, a unit, accepted mints, a description, and one or more delivery transports.

With an HTTP transport, the wallet sends the payment payload to the specified endpoint using a POST request. The payload contains the mint, unit, and ecash proofs, along with optional information such as a payment ID. The receiver can then validate and redeem the payment. These fields and the HTTP transport are defined in NUT-18, the Cashu payment request specification.

The useful detail for web applications is that the transport target is an ordinary URL. The application can choose that URL to identify the thing being paid for.

Example 1: Topping up an account

Imagine a web service where users maintain an account balance. They might use that balance to pay for API calls, storage, or some other service over time.

A conventional payment flow could create a new payment record every time a user wants to top up. The application would generate a payment request, wait for a settlement notification, look up the pending payment, and finally credit the corresponding account.

With a Cashu HTTP payment request, the service can instead give each account a reusable payment destination:

https://topup.example/pay/account123

The request could say, in effect:

Send any amount of sats from mint X to this endpoint.

Because the request does not specify an amount, the user can choose how much to send. The account ID is already part of the delivery URL, so the receiver knows where to apply the payment when it arrives.

The handler can then:

  1. Identify the account from the URL.
  2. Check that the payment uses the accepted mint and unit.
  3. Swap the received proofs with the mint.
  4. Credit the value actually received to the account.

The payment request can be generated once and reused. It does not need a separate pending payment record for every top-up, and the request itself does not have to expire. The application still maintains the user’s balance and records incoming payments, but it does not need to create and track a new payment attempt just to associate each incoming token with the right account.

That makes the request feel much like a deposit address, while delivering the payment directly to an application endpoint.

Example 2: Paying for an order

Orders introduce a little more state. A shop already needs to know what a customer ordered, how much they owe, and whether the order has been paid. Cashu does not remove that business logic, but the payment request can fit neatly into it.

Suppose a customer places an order for 5,000 sats. The shop creates a payment request that specifies the amount, unit, accepted mint, and an HTTP transport such as:

https://shop.example/pay/order456

The customer scans or opens the request in their Cashu wallet. The wallet constructs a matching payment and sends it to the endpoint.

When the payment arrives, the shop looks up order456, checks the payment against the order’s requirements, and swaps the proofs with the mint. Once it has successfully received the required value, it marks the order as paid and continues with fulfillment.

The shop does not need a separate mapping from a payment identifier back to an order: the order ID is already in the delivery address. It can use the order state it already maintains to decide what to do with the payment.

There are still details to handle. The shop needs a policy for payments that arrive after an order has expired or been paid, and for payments that are too small or too large. It also needs to handle retries and failures without crediting or fulfilling an order twice. The payment request simplifies how the payment is routed; it does not make those application decisions disappear.

Putting context in the transport

Both examples use the same idea: the payment destination identifies the resource, while the payment request tells the wallet how to pay it.

For a top-up, that resource is an account. For a checkout, it is an order. The same pattern could be used for a donation campaign, an event registration, or any other resource a web application can identify with a URL.

This is a small design choice with useful consequences. The receiver does not always need to create a separate payment object and later reconcile it with the rest of the application. In many cases, it can send the payment directly to an endpoint that already has the context needed to process it.

Cashu payment requests bring together payment requirements and delivery instructions in a format a wallet can understand. By making the transport target meaningful to the application, they give web developers a straightforward way to connect ecash payments to the things users are paying for.