James Stanley

How (and why) to accept Bitcoin payments yourself (without running a full node)

Fri 11 November 2016

Accepting Bitcoin payments for an online service can be daunting at first (I worked out how to do it, for SMS Privacy, a couple of months ago). What I describe is not the only way to accept Bitcoin payments - it's not even the best way to accept Bitcoin payments - but it works for me. And it might work for you, too.

Why handle payments yourself?

If you control the Bitcoin keys, it's your Bitcoin; if you don't control the Bitcoin keys, it's not your Bitcoin.Andreas Antonopoulos

Third-party Bitcoin payments services typically take a 1% fee (update 2016-11-12: this is not accurate). This is better than credit card processing (and without the risk of fraud or chargebacks), but we can do better. Using a payments service also means trusting that service (a) not to steal your money, and (b) not to lose your money.

But mainly it means asking permission: if the payments service doesn't like you, or you can't provide enough identity verification, or you flag up the wrong security check, they can simply deny you service. A large part of the attraction of Bitcoin, for me (and I suspect for many), is that you don't have to ask anybody for permission. If you want to use Bitcoin, you just use Bitcoin. And there isn't anyone who can stop you.

Why not run a full node?

Running a full node is the "correct" thing to do.

But I've tried to run a full node before and always been plagued by crashes and corruptions long before it had finished syncing. The network relies on full nodes - without full nodes there is no network - but for me the cost of running a working full node is just too high, so for now I find it easier to work without one.

How to do it

You will most likely need some prior experience of at least using Bitcoin. You will also need to do some programming to automate the process.

Generate addresses and private keys

I use the keyconv program from vanitygen as a convenient way to do this. Simply running "keyconv -G" generates an address and private key and outputs them.

(If you're using a RHEL-based system then you may find that vanitygen and keyconv segfault instead of doing anything useful. This is because the packaged version of OpenSSL is crippled due to "patent concerns" and does not contain the secp256k1 elliptic curve. The path of least resistance around this is to compile OpenSSL yourself and link keyconv against your own version.)

Associate addresses with users

Reusing addresses has security and privacy implications (read more here), so you want to generate a fresh address for the user after every payment you receive, but you still want to accept payments to old addresses in case the user reuses them. So make sure you can handle multiple addresses per user.

You would ideally have a page showing the user the payments they've made, including unconfirmed ones, with a link to the transaction on blockchain.info or equivalent. This isn't strictly necessary, but it is nice for users, and gives them confidence in your ability to correctly process their payments.

Check for incoming payments

This is the tricky part. The best solution is to run a full node so that you have a local, indexed copy of the blockchain that you can query at will. The solution I use is to check for payments in a 1-minute cron job using electrum.

My first idea was to check for payments by iterating over the list of addresses and calling "electrum getaddresshistory $address", but this becomes too slow to be practical after about 50 addresses.

A better option is to create a watch-only wallet, watching the payment addresses, and then query the wallet for its history. The output of "electrum history" is some JSON describing the history of transactions for the wallet. For any new transaction ids, you can call "electrum gettransaction $txid" to get the serialised form of the transaction, and then "electrum deserialize $txn" to learn the value and outputs of the transaction.

Example with only one address (134BLMLjWNDhEQHxKV2pv6Z2Cq8UdSN2d9):

$ electrum -w tmpwallet.dat restore 134BLMLjWNDhEQHxKV2pv6Z2Cq8UdSN2d9
Recovering wallet...
Recovery successful
Wallet saved in '/home/jes/tmpwallet.dat'

$ electrum -w tmpwallet.dat history [ { "confirmations": 2982, "date": "2016-10-22 12:34", "label": "", "timestamp": 1477136093, "txid": "18e3535ef545a1bdf8e689edbe913fe69049147e75ebad01f657185ac5766b7e", "value": 0.2 } ]

$ electrum gettransaction 18e3535ef545a1bdf8e689edbe913fe69049147e75ebad01f657185ac5766b7e { "complete": true, "hex": "01000000011702f547056eb8da406592ebcb363f5287dfdad41e0b64d0d29a5e5d038cc46a000000006a47304402202e408edea5c44dca5da17cddd70be3967cd42cde80ab8032011b0055c8a6125d022052a0316c6cf818d893afe9c6a4110b0ae67756b04afa4acc52acc52204cd8b65012103f3afd6ee54dbfe17676374850834afc33ee211ed360eb770583da1a9ba0595edffffffff0254991c00000000001976a91423782fb9e89233cc4bb940fd5de0c7650629020a88ac002d3101000000001976a914168a202310a4c3ada521c07a9ea3caa828fe1d0188ac00000000" }

$ electrum deserialize 01000000011702f547056eb8da406592ebcb363f5287dfdad41e0b64d0d29a5e5d038cc46a000000006a47304402202e408edea5c44dca5da17cddd70be3967cd42cde80ab8032011b0055c8a6125d022052a0316c6cf818d893afe9c6a4110b0ae67756b04afa4acc52acc52204cd8b65012103f3afd6ee54dbfe17676374850834afc33ee211ed360eb770583da1a9ba0595edffffffff0254991c00000000001976a91423782fb9e89233cc4bb940fd5de0c7650629020a88ac002d3101000000001976a914168a202310a4c3ada521c07a9ea3caa828fe1d0188ac00000000 { "inputs": [ ...snip... ], "lockTime": 0, "outputs": [ ...snip..., { "address": "134BLMLjWNDhEQHxKV2pv6Z2Cq8UdSN2d9", "prevout_n": 1, "scriptPubKey": "76a914168a202310a4c3ada521c07a9ea3caa828fe1d0188ac", "type": 0, "value": 20000000 } ], "version": 1 }

By parsing this JSON we can learn the value (which is given in satoshis in the output of electrum gettransaction; the example shown is 0.2 BTC) transferred to our addresses, and the number of confirmations. Unconfirmed transactions show up with confirmations: 0.

Although unlikely to happen, it is possible for one transaction to pay multiple user accounts on your service, and you should make sure you handle this appropriately (e.g. make sure you don't assume txids map to payments 1-1).

And in case it's not obvious: make sure you ignore transactions that spend from your users' addresses - this happens when you sweep the balance into your wallet.


To spend your money, just periodically sweep all of your payments into your own wallet so that you can spend the money conveniently. The Electrum user interface has a tool to sweep private keys: just paste a list of private keys in there and it will sweep them to your wallet.

And on security: anybody who compromises your server can simply modify your web app to display their own payment addresses to users, regardless of what security measures you put in place surrounding the generation/storage of the Bitcoin keys. If your server gets compromised, you can probably assume that any subsequent payments can be stolen. But that doesn't mean previous payments need to be vulnerable. I use RSA, with only the public key present on the server, to encrypt the Bitcoin keys before writing them to disk. When I sweep the payments, I decrypt using the private key which is kept in my "cold storage" (i.e. on a USB stick). This means there are no Bitcoin keys available on the server, so previous payments can't be taken.

Accepting Bitcoin payments without relying on a payments service is feasible for any sufficiently-motivated developer. Running a full node is preferable where possible, but if you can't run a full node, you're not automatically SOL.

If you can see any security (or other) flaws in my system, or have any ideas for improvements, or just want to talk about Bitcoin, please email james@incoherency.co.uk. Thanks for reading.

Update 2016-11-12: A couple of people mentioned using HD wallets: I initially tried using an HD wallet, but the mechanism for interacting with it was more inconvenient than generating keys manually - especially considering that you might have to generate hundreds of addresses in a row that never receive a payment. If you find it easier to run it using an HD wallet, then that's a perfectly good alternative.

If you like my blog, please consider subscribing to the RSS feed or the mailing list:

James Stanley - james@incoherency.co.uk | jesblogfnk2boep4.onion | [rss]