Skip to content
buybitcoinsmart

Glossary / Developer reference

PaymentDetails

Definition
PaymentDetails is the inner BIP70 message that actually described a payment: which outputs to pay, how much, when the request expires, and where to send the signed transaction.

Specified in BIP-70.

PaymentDetails was the part a merchant actually signed, and the outer envelope only carried the certificate that proved it. It held seven fields: a network name defaulting to main, a list of outputs, a required timestamp, an optional expiry, a memo, a callback URL and an opaque merchant_data blob. A BIP21 URI plus a checkout page now does the same job with no signature and no server.

How it works

Status: historical. Bitcoin Core removed BIP70 in version 0.20.0 on June 3, 2020, and PaymentDetails has no live implementation. It is documented here because the field names still appear in old integration guides and in the bitcoin.org developer reference.

Protocol Buffers, not JSON, carried the structure. That choice mattered for a reason worth pausing on: the merchant serialized PaymentDetails to a byte string and placed those exact bytes inside the outer PaymentRequest as an opaque field rather than as a nested message. Signing covered the byte string, so no library re-encoding the same fields in a different order could invalidate a valid signature.

The seven fields divided into instructions and bookkeeping.

  • network, a string defaulting to "main", with "test" the only other defined value.
  • outputs, a repeated list, each entry pairing an amount in satoshis with a script. More than one output was legal, and merchants used that to split a single customer payment between a hot wallet and cold storage without asking the customer to send twice.
  • time, a required unix timestamp recording when the request was created.
  • expires, an optional unix timestamp after which a wallet was supposed to refuse. This is the field that made BIP70 feel like a card terminal, because a merchant could quote a fiat price and guarantee it for a fixed window.
  • memo, a UTF-8 note displayed to the payer.
  • payment_url, where the wallet posted its Payment message after broadcasting.
  • merchant_data, opaque bytes echoed back untouched so the merchant could match a payment to a shopping cart.

Only time and the output scripts were mandatory. Everything else a merchant could omit, and most did.

Where you see it

The idea behind PaymentDetails outlived the message, which is the useful thing to notice.

Price expiry is now handled at the exchange rather than in your wallet. Buy through an instant-buy screen and you get a quoted rate with a countdown next to it; let the countdown run out and the quote is rebuilt at the new price. That is expires reimplemented in a web app, and it protects the seller against volatility in exactly the same way. Order matching on an exchange order book does the same job differently, by never quoting a fixed price at all.

The merchant-side equivalent of the whole message is an invoice record in checkout software. BTCPay Server and similar tools generate a BIP21 URI, hold it open for a configured number of minutes, and reconcile the incoming transaction against an internal order identifier, which is precisely what merchant_data was for.

PaymentDetails vs receipt

PaymentDetails was the request; the receipt in BIP70 was the PaymentACK the merchant returned afterwards. One described what should happen, the other confirmed that the merchant had seen it happen. Neither was a blockchain fact, and that is the limitation people underestimated: a signed acknowledgement from a merchant proves the merchant's server said something, not that a transaction confirmed. Confirmations, not receipts, are what settle a bitcoin payment.

Not to be confused with

Frequently asked questions

Why did BIP70 store PaymentDetails as raw bytes instead of a nested message?

So the signature covered an exact byte string. Protocol Buffers allow the same fields to be re-encoded in different valid ways, and signing over the serialized blob meant no library could accidentally break a valid signature by re-serializing it.

Related terms

More in Developer reference