Data Privacy in ConnectStats

I recently saw a negative review on the App Store for ConnectStats warning users that ConnectStats is not a Garmin app and therefore people should avoid giving away login information in the app as the data may get stolen.

Keeping data secure both on your phone or online has been a key guiding principle in how I tried to implement the app. So while I understand the concern, I felt it was a bit unfair.

I have been careful to make sure the data isn’t shared and the passwords are never sent to me. I also made the app open source so that people can check for themselves what it is doing.

I felt it may be worth to write a bit of details on what is ConnectStats doing with your data and password, with link to the code. So people can either let me know if I miss something or feel better about using the app.

Your login information

Garmin Website service

The app can connect directly to the Garmin website to retrieve information. In order to do that it needs to have access and store your username and password. To do that it stores the password in the keychain of your phone and never locally in a way someone looking at the files saved from connectstats could retrieve. It then relies on the iPhone keychain mechanism which Apple can ensure is secure. The key file to look at to see how it’s done is GCAppPasswordManager.

Strava and Withings services

For the Strava and Withings service, the authentication process uses the OAuth 2.0 so the password is never even seen by ConnectStats. The library I use to manage the OAuth 2.0 is provided by Google , and you can see in the file used by connectstats how the tokens are retrieved from the keychain in this file for Strava for example via this call:

ConnectStats service

ConnectStats also maintains a service that can receive the fit file from the new Garmin Health API. In that case the the authentication is done via OAuth 1.0a. So the passwords are never seen by ConnectStats or its web server, but only tokens are exchanged with Garmin. These tokens are then saved into the keychain of your phone as well as on the database in the server. Note that the server is open sourced as well. While the server is open sourced, the configuration files containing the database passwords and other secret keys are only saved on the server and not in the code. The website is hosted on Godaddy, a reputable company, and I rely on their security to make sure the access to the website is secured.

Your Activities Data

On the phone

Your activity data is kept on the phone and stored locally. So it will be as secure as you keep your phone. You can also see that if you try to run the app in airplane mode all the browsing of statistics and downloaded data will work. Of course you need a connection to download new data…

On connectstats server

With the new Garmin Health API service, ConnectStats needs to maintain a database in a server containing your activities. This is the case if you choose in the app Garmin as a service and source to be All or ConnectStats. If you choose Garmin WebSite only then the data will be accessed directly from the Garmin servers. Note that this is not the officially supported method from Garmin, can and has been subject to outage in the past due to undocumented changes to their website.

If the data is stored on the ConnectStats server, the access to that data is done via an OAuth 1.0 process. Both the app and the server keep a secret token, and use that to do the authentication. The tokens are provided by Garmin, so in order to access your data you will need to do a successful login on the Garmin service and obtain the token this way.

ConnectStats does not maintain any types of user name or its own passwords/user system, which means the data stored on the server can not be traced back to you. Everything is linked and identified by the sha1 hash tokens obtained by Garmin, which look something like this aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d.

The only person with access to the database with your data is myself, and no one else helps me or has access to the login information. If that ever changes, I’ll make sure to talk about it in this blog.

Note that if you use the source for Garmin data to be both the website AND connectstats service (what I recommend), you will need to enter your login details in the app (as in the Garmin website section), but that data will stay on your phone in your keychain. It will never be uploaded to the connectstats server. So on the server it will still be impossible to link the data saved in the database to your Garmin user account, email or username as well.

Bug Reports

When you send a bug report, this will send the log information, which will look something like the below. This is mostly information that helps me see what has happened and try to understand the problems. You can see in the code everything that is logged by looking for calls to the function RZLog. No sensitive information, like password is logged.

You can choose before sending a bug report to include activities. If this is selected in addition the log above, the internal database of activities saved on your phone is sent as well. This contains all the high level data (distance, heart rate, timings, etc) that allows reconstruction of the statistics page. In addition it will include all the details of the currently selected activity in the detail page (only one full detailed activity). These details contains all the gps points.

Because when you send a bug report, I ask for an email address so I can reply to you, in this case that data could be traced back to an individual with that email. But as mentioned before, I am the only one that receive that email or have access to the files where they are saved on my server. This by the way is the same server hosted by GoDaddy where I have host all the data for ConnectStats.


I hope this will relieve any concerns any one could have about privacy of their data in ConnectStats.

Happy to answer any more questions, and of course if anyone finds holes or gap in how I implemented ConnectStats, feel free to reach out either by comment below or via email or GitHub issue.

ConnectStats Winter Sports Edition

I spent over a week in the Swiss alps, so it was clearly the time to update ConnectStats with better support for winter sports…

Garmin had made some changes to their API for winter sports, and reorganised a bit the types of activities available. I have a Fenix 6 and it has a nice new activity for backcountry skiing with climbing mode (at least I wasn’t aware before). So I updated ConnectStats to properly recognise it and added a new icon for it. I also made sure it display the elevation gain in the summary as this is more relevant for these type of activities.

I have also started to add new graphs for the slope gradient, started working on best rolling elevation gain, but this will be for a next release. For now, I wanted a first version out that properly recognise the activity type.

I also include a few bugs fixes for some error messages from Strava as well as a long standing crash bug which I somehow only recently understood thanks to a details bug report from a user (thank you!)

This is pending approval from Apple and should be released under version 5.3 soon

A few app updates

I have a few upgrades of the app about to be released (pending Apple approval). I have pushed minor updates of ConnectStats, a new version of FitFileExplorer

Few bugs out of the way

The version 5.2.2 will contain a few bugs fixes that people have been reported recently.

First it should synchronise better the changes that occurs on the Garmin Connect Website, specifically the names of the activities.

Second, I had some logic that resulted in the metabolic efficiency not be calculated for laps, but as a user pointed out, it’s can be an interesting to look at the changes of the quantity over each laps through the activity. I had added this a long time ago, it represent in percent the amount of energy captured by a power meter divided by the amount of energy spend by the body in from the calorie calculation which is typically a function of the heart rate. How much of the energy the body generates is actually used to move on the pedal (bike power) or to generate the run motion (run power).

I have also started fixing a few issues in how units are handled to avoid erroneous display of pace in speed unit and other weirdness.


I have also pushed a new version of FitFileExplorer, the open source macos application I publish on the App Store to open and look at Fit Files on your Mac. It is mostly a convenience tool but extremely useful for debugging or looking a what is the raw information on a fit file. A nice little sister application of ConnectStats for the Mac, useful for its development.

Beside a bunch of internals option improvement, but also the ability to download files directly from the Garmin website from the app (using the logic of ConnectStats to access data). Just another convenience.

Here is a gallery of the type of way you can explore fit files

Learning from Services Woes

So in the last few weeks, ConnectStats had a lot of issues related to online services. That was quite a learning experience. I hope most of it is behind, but could be a few left overs.


Strava made a change in its API related to how it authenticate users for access. This was a documented and announced changed, unlike a lot of the Garmin API changes in the past,. The issue is mostly that I hadn’t understood the change, so I didn’t get the right fix out in time. So I have only myself to blame…

Because I don’t use Strava in ConnectStats myself too much except for testing, I had not noticed that my initial fix for the API change only worked after you did a new login process. In my tests, I was always login from scratch and it was working. But many users that had already logged in before the change and my fix ended up stuck in a failure state. A workaround was to go to the config page, in the strava settings and force a log out, after the next login things should work again.

I also now have pushed a new version that will force a logout/login cycle when it is in the bad state and now have better and better understanding of the OAuth 2.0 authorisation protocol…

ConnectStats Service

So after the previous set of issues, the new service had managed to be stable for new users for a bit over a week. Everything was running smoothly, so I decided to extend the rollout to existing users as well.

This worked well for a week, I added close to 2000 users in one week, which is not bad. But that’s when I suddenly got a notification from my website service provider that my account was suspended because it was creating an overload of database resources…

So I called my service provider and I upgraded my service to the next tier.

This unfortunately didn’t work very well, I noticed that somehow I had received a huge surge of activities at once. While I still don’t understand why that happened, I reported it to Garmin for investigation, but the result was even the new server was overwhelmed.

So I did some scary “on the fly” surgery of the database, deleted what appeared like the spurious activities until everything came back to normal.

In the process I noticed that the new server, while it had less constraints on resources, was actually processing information much slower that the old one, which resulted in further issue because I had implemented a timeout for processing, which was constantly hit and therefore making the situation worse (after timeout it would kill a process and restart it from the beginning).

So I reached out to my website hosting provider who advised me to purchase yet the next tier of server, which I did.

Things got better, the spurious activities stopped coming and the server was happy processing request.

That said, I noticed from the app it was clearly still noticeably much slower to download activities files to ConnectStats. But at that point I clearly had a better server and there was no way I needed more power. So after some research on optimisation of MySql, there was some simple solution which I implemented and now everything seems to be happy and rolling.

I also now have learned a lot about building a scalable online service online from scratch…

Virtual Ride

Another aspect that is hard for me to test is the use of ConnectStats with Virtual Rides or Runs from Zwift. They clearly are becoming more and more popular as I had quite a few users reaching out to highlight issues with those.

Virtual activities broke when using the new service or strava as a service in the app. So I have pushed a new version of ConnectStats that attempts to fix this. As any feature I don’t use myself, it’s a bit tricky to fix, as I can’t test it. I have to blindly iterate through, push the fix and see from other users if it worked… Hopefully the latest release will fix all remaining issues, but if it doesn’t, I appreciate Zwift users patience and feedback until I get it right…

Getting the weather for an activity in ConnectStats

As of version 5.1 you can obtain weather information for Connectstats activities if you use the new service I implemented using Garmin Health API and DarkSky. But in order to access it there are a few things to know and do.

The weather will only be added to new activities, once you have started using the new service, which is not currently the default in the app.

To enable the new service, you should go to your config page in Service and accounts and change the Source for the Garmin information to be All as below.

Note that if you do that it should preserve the history you have already downloaded from the Garmin Connect website and only start downloading new activities from the new ConnectStats service. Note that in case of issue you can always revert to only using the Garmin Website as a source.

New activities after you have completed the first login will start having the weather information added.

The reason I didn’t back populate the weather on older activities is both technical, as trying to back populate the older activities was a bit tricky to handle and cost, as I was worried it would make too many request to the weather API and start costing too much.

New services and Withings working again

Finally I got withings and the new service for ConnectStats to work again with version 5.1! At the time of this writing version 5.1 is pending apple approval for release.


The Withings authentication process has been broken for a while…

It took me trying a whole new approach to do the authentification process for the withings API, a few exchanges with withings support and learning way more than I ever wanted to know about the OAuth 2.0 protocol to get it all in the right place again.

The key change I believe is that withings started to have the OAuth 2.0 authentification token to expire and the way I was using the authentification library was slightly incorrect. But before I tried to use that library correctly, I tried to upgrade to a more modern approach than the one I was using that had been declared obsolete. These though required Withings to make adjustment on their side (non http scheme), which they kindly did. I never got it to work. Ultimately, after studying the deprecated library, using the ever more useful proxy app Charles I finally figured out how to get things working again! In the process I also streamlined the way the setup screen works, fix the ability to logout and back in, and synchronised the interface into Strava to use the same new code.

New ConnectStats Service

To use the new Garmin Health API, I had to implement a server that would receive, process and save all the activities of ConnectStats users when their device uploaded them to Garmin.

I knew that processing these requests as they came in realtime was a bad shortcut in the first implementation of the service for the new Garmin API. I had not expected how bad the deadlock and timeout would be when used in real life as often many files would be sent to my server at once. I had adopted the “make it work, optimise later” approach, but here the optimise step was immediately needed…

While I had postponed implementing it, I knew what the right approach was: I had to add all the incoming requests from the service to a queue and process sequentially a few at a time.

To host my server, I am using an entry level service from GoDaddy, which is a great relatively cheap hosting but with fairly limited optionality in configuration. Which meant it wasn’t an option to bring some pre-built queuing system. I had to build it myself with the tools available…

It’s of course not advisable to implement such a common system yourself, but, well, I do all this to learn anyway… It was definitely interesting to figure out how to make sure it behaved in a robust manner in all the edge cases I could think of. As with Connectstats itself, I open sourced the code of the server. Hopefully it can be useful to someone interested in having a lightweight server to work quickly with the Garmin Health API or can help people struggling to implement their own.

I am releasing this now in version 5.1. In that version only new users will use the new service by default, but existing user will have to switch manually in the configuration page. Crossing finger that this time it will work better and scale…

Bonus with the new service

Beside resiliency to the next API change from Garmin on their web site, the new service allowed me to bring in weather information from DarkSky, so that’s an added bonus for using the new service. Because I now fully control the service that serves activities to Connectstats, it maybe possible in the future to add more such new features.

Thought on servers, hosting, services etc

This experience made me appreciate better the differences in services. I use a service from GoDaddy that is very simple, turn-key and fairly cheap. While it forces you to be a bit more nibble in the implementation, it was a bit interesting to compare it to using more scalable and more configurable services like DigitalOcean or AWS. I briefly considered, but it’s clear that it would require a non-significant learning process and higher costs. So I am sticking with GoDaddy for now, and hope the system will handle an increase of users without requiring me to upgrade to a fancier hosting service.

The weather service from DarkSKy is very nicely done and reasonably priced and free for the first 1000 calls per day. I anticipate based on the current number of users that the cost will remain small.

New Service not working well

A few days after releasing my new version that uses the new Garmin Health API and my own server, it’s not really a success story… My server is overwhelmed by the synchronisation and backfill requests…

I had initially planned to turn on the new service only for new users to avoid stressing the server too much, but still it does. So I have issued a new version that only doesn’t enable the new service by default.

For those who have issues with the service, just switch the Source in the settings for Garmin to be Garmin Website

I am going to do two things, try to optimise the way the server processes backfill, and add a way for ConnectStats to download the history from the website, and only process new activities from the new service. I hope it’s just that I did something silly in my implementation (very likely) and not that I will need to upgrade to a bigger infrastructure…

I feel bad for the new users of the app this weekend as this is not a great first experience of the app, but hopefully, soon it will be in a better place..

Dark Mode and New Service

I just pushed version 5.0 of ConnectStats to Apple for review. It will likely take a few days for the version to be approved. Hopefully it will be done in time for the iOS 13 release.

This version has two major changes. First the app has been updated with a new look for iOS 13 and support for dark mode. Second it can now use the new service I have been testing with a few users. Before you switch, please read the details below to make sure it’s for you.

Dark Mode

Version 5.0 has a major revamp of the way the interface is driven internally to enable much more customisation of the look of the app. This was driven first to be able to support the new dark mode from iOS13, but it may later help support more customisations.

In the settings you can now choose a theme. You’ll find a few so far:

  • Original: it just make the app look like it has been in the last 8 years…
  • Dark Mode: it is a mode that changes the look to a dark mode on iOS 12
  • Dynamic: it is a mode that changes between the above two on iOS 13 depending on the general mode of the app (light or dark)
  • Native iOS 13: This is a mode only available on iOS 13 that uses the standard colors of iOS 13. The main difference being in dark mode and some minor changes in the light mode. This is probably my preferred one so far, and when the app runs for the first time on iOS 13 it will change to this mode (after iOS update)

You can see a few previews of the iOS 13 dark mode here. If you find any issues with it, send a bug report or comment here.

New Service

I have been implementing a new service in the last few months using the new Health API from Garmin. The main benefit is that it is supported by garmin, as opposed to the current way ConnectStats access your activities by using the unsupported API that the garmin connect website uses. It will present a few benefit now or later:

  • we should not have unexpected failure of the app like this one or this one. This service is fully supported by Garmin
  • It give more control on the data exchange and provide the ability to implement new features like adding weather information (will come soon after this release) or notification (maybe, if it’s deemed useful)

The flip side is that currently it has a few shortcomings, so I do not recommend that everyone switches to it just yet, unless you understand the below issues:

  • The initial synchronisation of the account can be slow : about 2 min for every 2 months of activities you want to synchronise. There is nothing I can do about that, it is a limitation of the API.
  • Details of older activities that were recorded on an older device no longer associated with your garmin account will not be available. So only a summary but now maps or details graph. Garmin told me they are working on fixing this issue.
  • Extra information you have on the Garmin Connect Website, for example activity names, or edited activity type after the initial upload will not be seen by the new service.
  • It’s a new service I implemented myself, so it is possible, instead of unexpected failure because of a garmin change, it will could have some issues initially because of my own mistakes… But I’ll do my best to fix.

In light of the above limitations, I currently set up the app to only use the new service for brand new users. Existing users will continue on the old API and would have to switch manually to the new service in the settings and as in the below screen, switch the source to ConnectStats.

The new service is implemented such that will will understand the equivalence with activities downloaded from garmin connect. So you can switch back and forth between the two services and it should work and not be confused. You can also create a new profile to test it out.

I intend to implement soon a new version that will allow to more seamlessly get the data from the two services, which will be a bit of the best of both world…

Note that I encourage you to read the privacy policy to make sure you understand what happens to your data. The key point being, the new service does not keep any information whatsoever about who you are. At no point do I save your name, email, etc (the service actually never asks for it and never obtains it), but the service will save your fit files.

You can read a bit more about the new service here.

New Garmin Service Integration

Garmin recently announced a new Health API for third party developer. Given the issues I have been having extracting activities from the web service with regular changes to the API breaking the app, I jumped on the opportunity to try to have a more robust integration.

The new service does not let you access the activities from your account directly as ConnectStats has been doing via the web site API, instead it is a mechanism to provide the activities into your own service. Which meant that to use it, I would have to host on a server my own service that receives the data, stores it and make it available to the app.

The service is now integrated, and I will have to start rolling it out. I will initially roll it out via Test Flight and ask volunteer to do some testing to make sure the service works. It is quite scary as I could only test it with my set of activities and my Garmin account.

Principles of the integration

It is possible either to start a new profile where all the activities are downloaded from this new service or to only start downloading from this service new activities when you have older activities already downloaded from Garmin. It should handle past duplicate properly.

The data will be synchronised into a database online, but the app will not ask you any identification via email or others. All the data will be referenced via hashed keys linked to Garmin account, and I intend to prevent any access to your data if you didn’t successfully login to the Garmin service. Any way around this is a bug, and I will fix them as they are reported. The web service is also open source so for transparency people can verify I don’t do anything bad or point the bugs out.

There are a couple of short comings from the new service to understand.

History requires slow first synchronisation

When you first enable the service, it will contain no history of your activities. Upon first login, it will trigger a synchronisation of your previous activities with Garmin which is time consuming. It happens at a rate of 90 days per two minutes. So synchronising one year of activities it will takes about 8 minutes.

Also because it is impossible for the app to know how many years to ask in the past, when you setup the account, you’ll need to specify from which year you want to synchronise your activities, which will default from the previous year. If you select many years you’ll need to wait a about 8 minutes per year until the synchronisation is finished.

This is a rate limitation imposed intentionally by the Garmin service, so there is nothing I can do about it.

Only details for activities from the devices currently associated with your account

Currently the service will only provide details (from fit files) for activities that were recorded with a device that is currently associated with your Garmin account. So if you changed devices and unregistered the old device, you will no longer be able to download the detailed fit file and only have access to a summary of the activity.

This is an unintentional issue with the Garmin service, and Garmin support told me they will work on removing the limitation. I’ll update the post when the limitation is removed.

Testing volunteers

I will need some volunteer to test the service. I can’t release widely after just testing on my account. It is likely that there will be some issues initially, but it should be easy to maintain two profiles in the app: one from the current Garmin service and one from the new one for testing. It is also intended to let you switch back an forth between services, if you delete some activities from the app and trigger a synchronisation from the other services, only the missing activities should come from the previous service.

The activities on the Garmin Connect service will NEVER be touched or deleted by anything you do on the ConnectStats app.

You’ll find instruction to test the new build here