|
| 1 | +# GreenField API example with Node.JS |
| 2 | + |
| 3 | +The **[GreenField API](https://docs.btcpayserver.org/API/Greenfield/v1/)** (also available on your instance on `/docs`) allows you to operate BTCPay Server via an easy to use REST API. |
| 4 | + |
| 5 | +Note that you can partially generate clients in the language of your choice by using the [Swagger file](https://docs.btcpayserver.org/API/Greenfield/v1/swagger.json). |
| 6 | + |
| 7 | +In this guide, we will give dome examples how to use the GreenField API with Node.JS. |
| 8 | +Make sure that the token you're using has the permissions to execute the request. |
| 9 | + |
| 10 | +## Create a new user |
| 11 | + |
| 12 | +Creating a new user can be done by using [this endpoint](https://docs.btcpayserver.org/API/Greenfield/v1/#operation/Users_CreateUser). |
| 13 | + |
| 14 | +```js |
| 15 | +const btcpayserverurl = "https://mainnet.demo.btcpayserver.org" |
| 16 | +const apiendpoint = "/api/v1/users" |
| 17 | +const token = "APIKEYTOKEN" |
| 18 | +const headers = { |
| 19 | + "Content-Type": "application/json", |
| 20 | + "Authorization": "token " + token |
| 21 | +} |
| 22 | +const user = { |
| 23 | + |
| 24 | + "password": "NOTVERYSECURE", |
| 25 | + "isAdministrator": false |
| 26 | +} |
| 27 | + |
| 28 | +fetch(btcpayserverurl + apiendpoint, { |
| 29 | + method: "POST", |
| 30 | + headers: headers, |
| 31 | + body: JSON.stringify(user) |
| 32 | +}) |
| 33 | + .then((response) => response.json()) |
| 34 | + .then((data) => { console.log(data) }) |
| 35 | +``` |
| 36 | + |
| 37 | +## Create a new API key |
| 38 | + |
| 39 | +While we can use basic authentication to access the greenfield API, it is recommended to use API Keys to limit the scope of the credentials. |
| 40 | + |
| 41 | +For example: If we want to [create a new store](https://docs.btcpayserver.org/API/Greenfield/v1/#operation/Stores_CreateStore) we need the `btcpay.store.canmodifystoresettings` permission for the API key. |
| 42 | + |
| 43 | +You can do it through BTCPay Server UI (by browsing `/account/apikeys` of your instance), but let's do it via command line using [this endpoint](https://docs.btcpayserver.org/API/Greenfield/v1/#operation/ApiKeys_CreateApiKey). |
| 44 | + |
| 45 | +```js |
| 46 | +const btcpayserverUrl = "https://mainnet.demo.btcpayserver.org" |
| 47 | +const apiEndpoint = "/api/v1/api-keys" |
| 48 | +const permission = "btcpay.store.canmodifystoresettings" |
| 49 | +const token = "APIKEYTOKEN" |
| 50 | +const headers = { |
| 51 | + "Content-Type": "application/json", |
| 52 | + "Authorization": "token " + token |
| 53 | +} |
| 54 | +const apikey = { |
| 55 | + "label": "LABELNAME", |
| 56 | + "permissions": [permission] |
| 57 | +} |
| 58 | + |
| 59 | +fetch(btcpayserverUrl + apiEndpoint, { |
| 60 | + method: "POST", |
| 61 | + headers: headers, |
| 62 | + body: JSON.stringify(apikey) |
| 63 | +}) |
| 64 | + .then((response) => response.json()) |
| 65 | + .then((data) => { console.log(data) }) |
| 66 | +``` |
| 67 | + |
| 68 | +## Create a new store |
| 69 | + |
| 70 | +Now, we can use the api key to [create a new store](https://docs.btcpayserver.org/API/Greenfield/v1/#operation/Stores_CreateStore). |
| 71 | + |
| 72 | +```js |
| 73 | +const btcpayserverUrl = "https://mainnet.demo.btcpayserver.org" |
| 74 | +const apiEndpoint = "/api/v1/stores" |
| 75 | +const token = "APIKEYTOKEN" |
| 76 | +const headers = { |
| 77 | + "Content-Type": "application/json", |
| 78 | + "Authorization": "token " + token |
| 79 | +} |
| 80 | +const store = { |
| 81 | + "Name": "STORENAME" |
| 82 | +} |
| 83 | + |
| 84 | +fetch(btcpayserverurl + apiendpoint, { |
| 85 | + method: "POST", |
| 86 | + headers: headers, |
| 87 | + body: JSON.stringify(store), |
| 88 | +}) |
| 89 | + .then((response) => response.json()) |
| 90 | + .then((data) => { console.log(data) }) |
| 91 | +``` |
| 92 | + |
| 93 | +## Webhook implementation with Node.JS + Express |
| 94 | + |
| 95 | +You can use your Node.JS Express web application to receive webhook requests from your BTCPay Server. |
| 96 | + |
| 97 | +First you need a route so that your Node.JS application can receive POST requests. |
| 98 | +Based on how you set up the express server this should look somthing like underneath. |
| 99 | + |
| 100 | +```js |
| 101 | +app.post('/btcpayserverwebhook', (req, res) => { |
| 102 | + //do stuff here |
| 103 | +}) |
| 104 | +``` |
| 105 | + |
| 106 | +What's important is that the webhook (as statet in the documentation) delivers an HTTP-Header `BTCPAY-SIG`. |
| 107 | +You should in above function compare the `BTCPAY-SIG` with the actual data from the request body (as bytes). |
| 108 | +In your app.js (or similar) add following where you include requirements: |
| 109 | + |
| 110 | +```js |
| 111 | +const bodyParser = require('body-parser') |
| 112 | +``` |
| 113 | + |
| 114 | +and add following |
| 115 | + |
| 116 | +```js |
| 117 | +app.use(bodyParser.json({ |
| 118 | + verify: (req, res, buf) => { |
| 119 | + req.rawBody = buf |
| 120 | + } |
| 121 | +})) |
| 122 | +``` |
| 123 | + |
| 124 | +This makes sure that in req.rawBody the correct content is parsed so that you can compare the hashed req.rawBody with the `BTCPAY-SIG` header value. |
| 125 | + |
| 126 | +Edit your router function like this: (Obviously change `webhookSecret`) |
| 127 | + |
| 128 | +```js |
| 129 | +app.post('/btcpayserverwebhook', (req, res) => { |
| 130 | + const sigHashAlg = "sha256" |
| 131 | + const sigHeaderName = "BTCPAY-SIG" |
| 132 | + const webhookSecret = "VERYVERYSECRET" |
| 133 | + if (!req.rawBody) { |
| 134 | + return next('Request body empty') |
| 135 | + } |
| 136 | + const sig = Buffer.from(req.get(sigHeaderName) || '', 'utf8') |
| 137 | + const hmac = crypto.createHmac(sigHashAlg, webhookSecret) |
| 138 | + const digest = Buffer.from(sigHashAlg + '=' + hmac.update(req.rawBody).digest('hex'), 'utf8') |
| 139 | + const checksum = Buffer.from(sig, 'utf8') |
| 140 | + if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { |
| 141 | + console.log(`Request body digest (${digest}) did not match ${sigHeaderName} (${checksum})`) |
| 142 | + return next(`Request body digest (${digest}) did not match ${sigHeaderName} (${checksum})`) |
| 143 | + } |
| 144 | + else { |
| 145 | + // Do More Stuff here |
| 146 | + res.status(200).send('Request body was signed') |
| 147 | + } |
| 148 | +}) |
| 149 | +``` |
0 commit comments