Webhook your weight with Withings API
Tags: withings, javascript
Published: 2022-01-24 14:18:01


This is a short walkthrough on how to authenticate with and use the Withings API. Its purpose is to explain how to get going with the code in [this GitHub repo](https://github.com/GuTsaV/trumpet). The goal of the code is to be lightweight and have few dependencies, making it possible to run on weaker computers, often used in home automation. 

## Prerequisites
* You'll need a Withings product. I use a scale. The webhook is the same for all products.
* You'll also need a domain name pointing towards your server and an open port, or use ngrok. This will be your callback url.
* I write cURL commands below, if you prefer to use Postman, it has an Import button to convert from cURL. 


## Withings account setup

[Click here to create a partner account](https://account.withings.com/partner/dashboard_oauth2). You’ll receive a client id and a consumer secret, hereafter called client secret. 


## Withings authentication

The authentication flow contains quite many steps, and we'll cheat by running some requests manually, instead of implementing the whole process in code. [This links leads to the authentication documentation](https://developer.withings.com/api-reference/#tag/oauth2).

Using your client id, client secret and callback url, complete the following URL and enter it in a **browser**. 

```
https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=<your client id>&state=whatever&scope=user.info,user.metrics&redirect_uri=<your callback url>
```

Click *Allow this app*, which will redirect to your callback url with a query param called **code**. This is an authorization code and it is valid for 30 seconds. 

Then, fill out your values in this cURL command and run it. 

```
curl --data "action=requesttoken&grant_type=authorization_code&client_id=<your client id>&client_secret=<client secret>&code=<the authorization code from above>&redirect_uri=<your callback url>" 'https://wbsapi.withings.net/v2/oauth2'
```

You will receive a JSON response, containing a user id, an access token and a refresh token. The access token is needed to fetch data, and it's valid for three hours. The refresh token is used to get a new access token, and every time the access token is renewed, the refresh token is also renewed.


## The webhook server

### Persisting the user id and refresh tokens

As mentioned above, the refresh token is also renewed, thus demanding a way both to persist and update it. A database would be the best option, but this would require more dependencies. Instead, we'll persist the user id and refresh tokens in a file on disk. 

Two really simple methods is all that is needed, one for reading and one for writing. 

```javascript
const getRefreshToken = async (userId) => fs.readFile(`./${userId}`, 'utf8');

const persistRefreshToken = async (userId, refreshToken) => fs.writeFile(`./${userId}`, refreshToken);
```

The last step is also to create a file for your user id and current refresh token. Use this command: `echo '<refresh token>' >> <userid>`, for example: `echo 'xxyyzz112233' >> 1234`. 


### Getting an access token (and a refresh token)

Axios is used to make the API request. An axios instance for communicating with the Withings API is made, setting the defaults of the API. Note that the data is not sent as JSON, it is form-data.

```javascript
const withingsAxios = axios.create({
  baseURL: 'https://wbsapi.withings.net',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
```

The [`refreshAccessToken`](https://github.com/GuTsaV/trumpet/blob/master/index.js#L25) function fetches the current refresh token from file, uses it to obtain both an access and a new refresh token, and then persists the new refresh token. [The Withings endpoint used is described more here.](https://developer.withings.com/api-reference/#operation/oauth2-refreshaccesstoken)

```javascript
const refreshAccessToken = async (userId) => {
  const currentRefreshToken = await getRefreshToken(userId);

  const data = {
    action: 'requesttoken',
    grant_type: 'refresh_token',
    client_id: WITHINGS_CLIENT_ID,
    client_secret: WITHINGS_CLIENT_SECRET,
    refresh_token: currentRefreshToken,
  };
  const body = qs.stringify(data);
  const response = await withingsAxios.post('/v2/oauth2', body);

  const newRefreshAccessToken = response.data.body.refresh_token;
  await persistRefreshToken(userId, newRefreshAccessToken);

  return response.data.body.access_token;
};
```

### Fetching a measurement

The [`getMeasures`](https://github.com/GuTsaV/trumpet/blob/master/index.js#L46) function fetches mesurements from your Withings appliances. An access token must be supplied, as well as a start and an end date. The dates should be Unix timestamps. However, we don't have to worry about those, because the callback will include timestamps. 

Again, the axios instance is used, the data is form-data. The access token is included in the header. [This links to the documentation.](https://developer.withings.com/api-reference/#operation/measure-getmeas)

```javascript
const getMeasures = async (accessToken, startDate, endDate) => {
  const data = {
    action: 'getmeas',
    meastype: 1,
    category: 1,
    startdate: startDate,
    enddate: endDate,
  };
  const body = qs.stringify(data);

  const response = await withingsAxios.post('/measure', body,
    { headers: { Authorization: `Bearer ${accessToken}` } },
  );

  return response.data.body.measuregrps;
};
```


### Webhook server using Express

A simple Express endpoint is used to receive the webhook call. The call does not include any measurements, only timestamps, but your code will get a new access token and fetch the measurement data using the functions above, using the timestamps that were included in the webhook. 

```javascript
app.post('/withingsWebhook', async (request, response) => {
  response.send('ok');

  const userId = request.body.userid;
  const startDate = request.body.startdate;
  const endDate = request.body.enddate;

  const accessToken = await refreshAccessToken(userId);
  const measures = await getMeasures(accessToken, startDate, endDate);
  const weightInGrams = measures[0].measures[0].value;
  return weightInGrams;
});
```


### Subscribing to the Withings webhook

The last step is to tell Withings to post to your webhook. We will do this manually as well. 

```
curl --header "Authorization: Bearer <your access token>" --data "action=subscribe&callbackurl=<your callback url>&appli=1&comment=comment" 'https://wbsapi.withings.net/notify'
```

Finalizing this will have your webhook endpoint be called when a new measurement is made. There is, unfortunately, about 30 seconds delay from Withings. 


## The optional last steps

* Add it to cron to start at system reboot
* Publish your weight on mqtt
* Have Home Assistant receive it
* ...and configure Google Home to say it out load!
* Add error handling, and tests

For full code, head over here: https://github.com/GuTsaV/trumpet

{% youtube g0XYX7SInqM %}