Releases: the-momentum/open-wearables
Release list
0.7.0 - Google Health API & SensorBio
Highlights
We encourage you to use our official Docker images:
- backend: https://hub.docker.com/r/themomentum/open-wearables-backend
- frontend: https://hub.docker.com/r/themomentum/open-wearables-frontend
Google Health API & SensorBio
In our latest release, we’re introducing two new integrations with providers. Both integrations will be further developed, but it’s important to note two things:
- The Google Health API replaces the Fitbit API, which is soon to be deprecated by Google
- SenorBio does not currently support webhooks
Admin UI Dashboard improvements
On less efficient infrastructure, the front-end dashboard could take up to several seconds to load data. We optimised this by making improvements to React and removing unnecessary information. In return, we added more details about the volume of stored data and its specific types (see screenshot below). We also improved the display of large numbers (previously, even millions were shown in thousands). The dashboard should now load instantly, even with very large volumes of data and limited RAM.
More information on data sources for the frontend
On the frontend, each piece of data (workouts, sleep sessions, menstrual cycles, activity) will display more information. Until now, we’ve only shown the provider from which the data was saved. Now, the original provider will also be displayed (if applicable – for example, Apple allows data to be synchronised from devices belonging to other providers) along with the device from which the data was retrieved (currently, in the example described earlier, for Apple this will simply be an iPhone, but we also intend to start recording information about the original device soon, provided the providers allow it).
You’ll probably often notice gaps in this information; we intend to rectify them in the next release, as this requires changes to the way data is fetched from the sources.
The option to delete data associated with a selected provider
From now on, the user profile includes an option to delete data from a selected provider. This also automatically disconnects the connection.
SDK applications management in Settings
From now on, you can manage SDK applications via the frontend settings (Admin UI).
Logging improvements
The first change is an improvement to the SDK’s data-saving process. Previously, a single incorrect row would result in the loss of the entire batch. Now, instead, we display the sync status for the problematic rows and log their exact errors to Sentry.
In addition, we have added a lot of new information to the logs, which may be useful for debugging.
HTTP logs from app:
response_body(an error details)request_bytes(how many bytes we are expecting from the payload)error_cause_type(can be helpful to decide whetheran error parsing the bodyresponse_body is caused by connection or decode error)error_cause_msgcontent_type(should beapplication/json)content_encoding(for examplegzip)
{"timestamp": "2026-07-30T17:41:05.671050+00:00", "level": "error", "message": "http_request", "provider": null, "method": "POST", "path": "/api/v1/sdk/users/00000000-0000-0000-0000-0000000000bb/sync", "status": 400, "duration_ms": 17.7, "request_bytes": 3, "response_body": "There was an error parsing the body", "content_type": "application/json", "error_cause_type": "UnicodeDecodeError", "error_cause_msg": "'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}
{"timestamp": "2026-07-30T17:41:05.681616+00:00", "level": "error", "message": "http_request", "provider": null, "method": "POST", "path": "/api/v1/sdk/users/00000000-0000-0000-0000-0000000000bb/sync", "status": 400, "duration_ms": 0.6, "request_bytes": 62, "response_body": "There was an error parsing the body", "content_type": "application/json", "content_encoding": "gzip", "error_cause_type": "UnicodeDecodeError", "error_cause_msg": "'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte"}
{"timestamp": "2026-07-30T17:41:05.732281+00:00", "level": "error", "message": "http_request", "provider": null, "method": "POST", "path": "/api/v1/sdk/users/00000000-0000-0000-0000-0000000000bb/sync", "status": 400, "duration_ms": 0.9, "request_bytes": 1000000, "response_body": "There was an error parsing the body", "content_type": "application/json", "error_cause_type": "ClientDisconnect", "error_cause_msg": ""}
Logs from celery worker:
first_error_loc(position of the first broken row in S3 payload)data_type(SDK related, exampleHKQuantityTypeIdentifierWalkingStepLength)success(SDK related, exampletrue)record_count(SDK related, int)task_type(SDK related, exampleforeground)low_power(SDK related, examplefalse)thermal_state(SDK related, examplenominal)types(sync status related, example["basal_energy", "energy", "heart_rate", "steps"])
{"timestamp": "2026-07-30T15:14:21.143378+00:00", "level": "info", "message": "Apple data import completed", "provider": "apple", "action": "apple_sdk_import_complete", "batch_id": "89d47924-a674-48bd-9001-32c3ec3065e2", "user_id": "3c213ef4-38f3-4c0c-89d3-c782a741125d", "incoming_records": 1750, "incoming_workouts": 125, "incoming_sleep": 125, "records_saved": 2023, "workouts_saved": 124, "sleep_saved": 124, "validation_ms": 56.5}
{"timestamp": "2026-07-30T15:14:21.143502+00:00", "level": "warning", "message": "Apple SDK dropped invalid records", "provider": "apple", "action": "apple_sdk_records_dropped", "batch_id": "89d47924-a674-48bd-9001-32c3ec3065e2", "user_id": "3c213ef4-38f3-4c0c-89d3-c782a741125d", "dropped_count": 3, "first_error_loc": "records[0].value", "first_error_msg": "Input should be a valid decimal"}
{"timestamp": "2026-08-11T11:20:50.019232+00:00", "level": "info", "message": "SDK log events received",
"provider": "apple", "action": "sdk_logs_received", "batch_id": "823f86a1-b76a-4f48-....",
"user_id": "d771d1c3-3098-....", "event_count": 2,
"event_types": ["historical_data_type_sync_end", "device_state"], "sdk_version": "0.13.0",
"data_type": "HKQuantityTypeIdentifierWalkingStepLength", "success": true, "record_count": 966,
"task_type": "foreground", "low_power": false, "thermal_state": "nominal"}
{"timestamp": "2026-08-11T11:21:33.975802+00:00", "level": "info", "message": "Sync status",
"provider": "apple", "action": "sync_status", "status": "success", "source": "sdk",
"stage": "completed", "run_id": "a899503a-e7bb-....", "user_id": "d771d1c3-...",
"items_processed": 1439, "types": ["basal_energy", "energy", "heart_rate", "steps"],
"detail": "Apple batch saved"}
What's Changed
Google Health API
SensorBio
Oura
Data model
- perf(backend): remove event_record_detail table by @itsnotashtonkutcher & @KaliszS in #1314
Frontend improvements
- fix(frontend): emit root stylesheet from client build by @aritsyg in #1315
- fix: include icon_url on user connections by @Gasiek in #1347
- feat(backend): add missing Apple and Samsung provider icons by @bartmichalak in #1369
- fix(frontend): load theme colors under Tailwind v4 by @MichalZak17 in #1376
- refactor: optimise dashboard by @KaliszS in #1388
- refactor(frontend): optimise react by @KaliszS in #1389
- refactor: data points count K->M->B->T by @KaliszS in #1390
- feat(frontend): event records in dashboard by @KaliszS in #1391
- refactor(frontend): replace hsl(var(--x)) workarounds with theme utilities by @MichalZak17 in #1397
- refactor(frontend): migrate Tailwind theme to CSS-first @theme b...
0.6.3 - Nap detection & HTTP logs middleware
Highlights
We encourage you to use our official Docker images:
- backend: https://hub.docker.com/r/themomentum/open-wearables-backend
- frontend: https://hub.docker.com/r/themomentum/open-wearables-frontend
Changes to HTTP logs
As HTTP logs usually account for more than 80% of all logs, we have disabled them by default for production deployments and added a new variable to control their flow (ACCESS_LOG_LEVEL). How it works:
ACCESS_LOG_LEVEL=all-> All HTTP logs are available, as beforeACCESS_LOG_LEVEL=errors-> only 4xx & 5xx log entries are availableACCESS_LOG_LEVEL=off-> no HTTP logs- no
ACCESS_LOG_LEVEL(None value):- if
ENVIRONMENT=production->errorslevel is set automatically - else
alllevel is set automatically
- if
We have also changed the structure of the HTTP logs:
- before:
[2026-07-15 11:41:55,669 - uvicorn.access] (INFO) HOSTNAME:PORT - "GET /api/v1/users?page=1&limit=9&sort_by=created_at&sort_order=desc HTTP/1.1" 200
- after:
{"timestamp": "2026-07-15T13:31:03.742767+00:00", "level": "info", "message": "http_request", "provider": null, "method": "OPTIONS", "path": "/api/v1/users?limit=5&sort_by=created_at&sort_order=desc", "status": 200, "duration_ms": 3.5}
Warning
By default, ACCESS_LOG_LEVEL is set to None, so if you have the ENVIRONMENT variable set to production, you will stop logging most messages. If you wish to retain the current behaviour, you must add the ACCESS_LOG_LEVEL variable and set it to all.
Note
For the time being, this change only affects HTTP logs from uvicorn. The new variable does not alter the behaviour of logs from Open Wearables.
Nap detection for Garmin & Oura
We have added support for naps based on data provided by Garmin and Oura. For the time being, this feature works correctly only for these two providers, but we will endeavour to develop our own algorithms to detect naps for providers that do not themselves flag which sleep sessions are naps.
Flag to enable/disable outgoing webhooks
We have added a new variable, OUTGOING_WEBHOOKS_ENABLED, which allows you to enable or disable outgoing webhooks. By default, it is set to false, so you need to add it and set it to true for the webhooks to start working.
Note
Please note that this has nothing to do with incoming webhooks from providers. It only concerns events that Open Wearables sends to client backends.
Strava workout samples
Once the INGEST_WORKOUT_SAMPLES variable is set to true (it was added in previous versions whilst working on Garmin workout samples and is set to false by default), Strava will start logging workout data at a high frequency. You can find out more about this mechanism in the entry “Ingest workout samples” for Garmin in the 0.6.0 release note: https://github.com/the-momentum/open-wearables/releases#release-0.6.0
Redis switched from RDF to AOF mode
We have changed the way Redis works to avoid errors in certain edge cases. You can read the details here: #1188. We have, of course, ensured that the data stored in Redis is preserved during the update.
The Docker Dev image has been reduced in size by around 40%
We achieved this by sealing the .dockerignore file and removing old and unused layers from the Dockerfile. This, of course, only applies to the local development version; nothing changes in the production build.
What's Changed
Outgoing webhooks
- fix(backend): add event emission after reconnection by @itsnotashtonkutcher & @KaliszS in #1262
- feat(backend): silence svix logs at startup by @KaliszS in #1289
- feat(backend): outgoing webhooks enable flag + manual disconnect event by @KaliszS in #1293
Apple
Oura
- fix(backend): persist daily resting heart rate from sleep data by @andrewle21 & @KaliszS in #1176
- feat: Add nap detection for Oura, Garmin by @czajkub in #1149
Suunto
- fix(integrations): accept float totalDistance in Suunto workout schema by @thecodearrow in #1253
Garmin
- fix(backend): remove dead sleep parameters from Garmin by @KaliszS in #1275
- feat: Add nap detection for Oura, Garmin by @czajkub in #1149
Strava
- feat(backend): Strava workout samples by @Ri087 in #1093
- fix: Strava samples coverage by @czajkub in #1283
Fitbit
API improvements & optimisation
- refactor(backend): update get method to accept string as object_id by @curiousmoonnadin & @KaliszS in #1265
- fix(backend): accept summary_start_time/summary_end_time in provider load_data by @haoyu-haoyu & @KaliszS in #777
- fix(backend): n+1 in sleep summary by @itsnotashtonkutcher & @KaliszS in #1259
- fix: update sleep summary endpoint by @czajkub in #1257
- feat: added Women's Health to Data Coverage tab by @KaliszS in #1285
Observability & deployments
- fix(backend): enable Redis AOF persistence so the svix queue survives restarts by @knowald & KaliszS in #1188
- fix(frontend): remove redundant layers & add .output to .dockerignore by @KaliszS in #1296
- feat(backend): middleware for uvicorn logs by @KaliszS in #1297
- feat(backend): timestamp for HTTP middleware logs by @KaliszS in #1301
MCP
Docs & chores
- feat: automatic update for coverage docs by @KaliszS in #1286
- docs: remove custom deployment note from Garmin warning by @bartmichalak in #1254
- chore: update frontend dependencies by @KaliszS in #1270
- chore: update backend dependencies by @KaliszS in #1271
- docs: sync .env.example with config.py + CodeRabbit drift check by @aks129 in #1264
- docs: remove custom deployment note from Garmin warning by @bartmichalak in #1272
- docs: remove Garmin API integration page from nav menu by @bartmichalak in #1273
- chore(frontend): update ts & prettier by @KaliszS in #1284
- docs: clear out roadmap page, coming back soon by @bartmichalak in #1294
- chore: update Open Wearables to 0.6.3 by @KaliszS in #1302
New Contributors
- @aks129 made their first contribution in #1264
- @curiousmoonnadin made their first contribution in #1265
- @itsnotashtonkutcher made their first contribution in #1262
- @thecodearrow made their first contribution in #1253
- @andrewle21 made their first contribution in #1176
- @Ri087 made their first contribution in #1093
Full Changelog: 0.6.2...0.6.3
0.6.2 - Data integrity
Highlights
Counting steps and energy
We have updated the summaries for step count and energy (kcal). The data in the data_point_series table now includes a new is_daily_total column. This is because data providers supply data at varying levels of granularity. Previously, an error occurred during aggregation when some providers sent a daily entry alongside granular entries (e.g. Garmin in 15-minute chunks). From now on, these will be correctly flagged via the new column. The current logic for displaying totals in summaries means that the daily record will be used first. Only if this does not exist will all other entries from that day be summed.
Warning
The next time Open Wearables is launched, an automated script will set the value to true in a new column for the relevant rows in the data points. Intraday aggregates are not flagged as false by the script, so as not to place too much load on the application at start-up (although all new records will be). Therefore, the value none in the is_daily_total column should be treated as equivalent to false.
Active minutes
From now on, active_minutes will be treated as a separate series type and retrieved directly from all providers that supply this data. Until now, we have defined active minutes as the number of minutes in which more than 30 steps were recorded. This definition will remain the fallback for providers who do not supply this data. Our science team is developing a new, improved method for calculating this parameter, which will be implemented in the near future.
Warning
This change will not affect any data that has already been saved to the database. To retrieve correct active minutes for this data, you will need to resynchronise it. With Garmin, you must first delete the data, whereas with other providers, it will be overwritten during historical synchronisation.
Tip
Providers that directly support active minutes include: Garmin, Oura, Polar and Ultrahuman.
Filter sleep sessions by priority
Important
One API endpoint (/users/{user_id}/events/sleep) has been updated. However, this is a backwards-compatible change as a new parameter has been added that is set by default to not alter the existing behaviour of the endpoint.
We have added an optional filter_by_priority flag to the /events/sleep endpoint. This flag is disabled by default and, when enabled, filters out duplicate sleep sessions. On our frontend, this option is available as a toggle, as shown in the screenshot below. The screenshot illustrates a situation in which Oura connects to both Apple Health and Open Wearables, meaning sleep sessions are sent to the database directly from Oura and via Apple Health.
Garmin's respiratory rate
We have corrected the way this parameter is retrieved. Until now, we had been searching for it in raw payloads under the wrong name, meaning it was not being saved to the database.
Improved data summary per user
We have enhanced the visual design of the data summary section and added time filters, enabling you to filter your data summaries by day or by a specified time period.
Manual invitation links
Thanks to our contributor, we can now manually copy invitation links, which is useful if you don't have an email account configured.
connection.revoked outgoing webhook
A new event is now available in outgoing webhooks, which will notify you when connections with providers are revoked.
What's Changed
Data integrity
- fix(backend): take daily data OR sum intraday data by @KaliszS in #1232
- fix(backend): active minutes as a new series type by @KaliszS in #1242
- fix: correctly filter sleep sessions by priority on frontend by @czajkub in #1249
Oura
Apple Health
Garmin
Other Improvements
- fix: Mark connection as revoked on failed token refresh + connection.revoked outgoing webhook by @czajkub in #1203
- fix(backend): split iOS & Android coverage by @KaliszS in #1233
- feat: support manual invite links when email is not configured by @edsammy in #1230
- fix(frontend): optimise activity tab by @KaliszS in #1245
- refactor(frontend): improve data summary per user by @KaliszS in #1246
Docs & chores
- chore: update backend dependencies by @KaliszS in #1221
- chore: disallow indexing of docs Next.js and Vercel internal paths by @psobusiak in #1224
- docs: update Strava integration docs to reflect API terms changes by @bartmichalak in #1234
- docs: update resilience score by @anjazych in #1115
- chore: update Open Wearables version to 0.6.2 by @KaliszS in #1247
New Contributors
Full Changelog: 0.6.1...0.6.2
0.6.1 - Data Coverage & Observability
Highlights
Data Coverage tab
We have added a new Data Coverage tab to the admin panel which shows the data that we support for each provider. This information is always up to date.
Note
Although it includes OpenWearables capabilities, it does not display which data has already been synchronised with the system.
Observability, deployments & logs
The focus of this release was on making debugging and troubleshooting easier by improving the visibility of logs and sync statuses. These changes include the following:
- adding a workflow to publish an image to Docker Hub
- linked webhook logs to users and/or traces (including failed attempts to receive a webhook).
- the application version has been added to Sentry logs
- improved reliability when saving sync statuses from webhooks to Redis (new
skippedstatus added). - new metadata has been added to sync statuses, dividing new items into
insertedandupdated.
Lookback for pull syncs
Although we recommend using webhooks, we have added lookbacks as a safeguard against data gaps if you need to use periodic pull syncs for any reason. This could happen if a user has not opened their wearables app for a long time, causing the live sync window to expire. This issue can now be resolved by syncing data from a specified number of minutes, hours or days in the past.
In order to use this feature, you will need to set the PULL_SYNC_LOOKBACK environment variable, specifying a string containing a number and a time unit. For example, 2d would mean 2 days, 3h would mean 3 hours, and 15m would mean 15 minutes. In periodic pull mode, live sync by default retrieves data recorded from the start of the day on which it is called until the present moment. Setting the lookback extends the start of the sync window by the specified duration and unit of time.
Note
PULL_SYNC_LOOKBACK is disabled by default.
This session is in committed state error
Thanks to our new contributor, the bug that caused ORM objects to be downgraded has been fixed.
What's Changed
Observability, deployments & logs
- ci: add manual Docker Hub image publish workflow by @kczpl in #1179
- fix(backend): build data source display name from reloaded provider strings by @knowald in #1154
- chore(backend): add user/trace correlation to webhook logs by @bartmichalak in #1183
- feat(backend): set Sentry release from version + commit SHA by @bartmichalak in #1196
- docs: remove publishing the images section by @bartmichalak in #1205
- fix(backend): webhooks syncs being saved into sync logs by @KaliszS in #1209
- refactor: improved sync metadata logging by @KaliszS in #1211
- fix(integrations): drop non-retriable 4xx webhook deliveries instead of retrying forever by @slavarosin in #1053
Docs & chores
- chore(backend): replace Garmin provider icon with brand-compliant logo by @bartmichalak in #1210
- chore: update OW version to 0.6.1 by @KaliszS in #1216
- chore: update frontend dependencies by @KaliszS in #1217
- chore: update backend dependencies by @KaliszS in #1218
Suunto
- fix(backend): report accurate Suunto sleep sync stats, skip unsaveable sessions by @bwickner in #1182
- fix(integrations): handle Suunto workout webhook edge cases by @HugoHeneault in #1042
- fix(backend): suunto tests by @KaliszS in #1189
Garmin
- fix(backend): persist body composition muscle mass from Garmin webhooks by @bartmichalak in #1194
- fix(backend): persist blood pressure from Garmin webhooks by @bartmichalak in #1193
Polar
- fix: accept float values for Polar training load fields (Fixes #1198) by @rtmalikian in #1204
Whoop
Fitbit
- fix(integrations): coerce Fitbit string start/end dates to datetime before strftime by @slavarosin in #1054
Other improvements
- feat: automatic data types coverage tab by @KaliszS in #1206
- feat(backend): lookback in pull sync by @KaliszS in #1215
- fix(frontend): empty-state hint for provider priorities list by @bartmichalak in #1191
- fix(webhooks): fire event record webhook directly instead of via after_commit by @zacharydrillings-atria in #1208
New Contributors
- @bwickner made their first contribution in #1182
- @rtmalikian made their first contribution in #1204
- @zacharydrillings-atria made their first contribution in #1208
Full Changelog: 0.6.0...0.6.1
0.6.0 - Women's Health & FIT files (Garmin)
Highlights
Women's Health
We now support menstrual cycle tracking for Garmin wearables. The new women's health tab is only available if the user has fetched any related data. Otherwise, it is invisible.
Ingest workout samples (high data granularity)
We have added an option to enable second-by-second granularity for certain data from Garmin, such as heart rate, speed, cadence, power, latitude, longitude, elevation and running dynamics (which require a FIT file), as well as air temperature (also requires FIT file to be available).
Caution
This option is disabled by default. To enable it, you must set the INGEST_WORKOUT_SAMPLES environment variable to true.
Warning
According to our tests, this will increase database memory usage by approximately 170 MB per user per month.
FIT file parser and saving to S3
A parser for FIT files has been implemented. Currently, there is no option to upload these files manually; they are processed automatically via Garmin webhooks. However, the same parser will eventually support other providers.
Garmin currently extracts the following data from FIT files and saves it to the database:
- training segments (laps, swim lengths, etc.)
- time spent in each heart rate zone during a workout
- time spent in each power zone during a workout
- air temperature (
INGEST_WORKOUT_SAMPLESrequired) - running vertical oscillation (
INGEST_WORKOUT_SAMPLESrequired) - running vertical ratio (
INGEST_WORKOUT_SAMPLESrequired) - running stride length (
INGEST_WORKOUT_SAMPLESrequired) - running ground contact time (
INGEST_WORKOUT_SAMPLESrequired) - running stance time balance (
INGEST_WORKOUT_SAMPLESrequired)
Note
We have added a new environment variable called STORE_FIT_FILES, which is set to false by default. When this is set to true, raw FIT files from Garmin will be saved to a previously configured S3 bucket. See the S3 configuration guide for more information: https://openwearables.io/docs/dev-guides/raw-payload-storage
Warning
Storing raw FIT files in S3 results in an additional data usage of between a few MB and a dozen or so MB per user per month.
Many Oura bugs fixed
We have fixed several bugs relating to the Oura integration. We have added time zone offset saving to the database, supported sleep summary metrics, improved data retrieval for current day summaries and migrated HRV from SDNN to RMSSD. Existing SDNN values in the database will be migrated by an automatic script that runs when the application starts.
Added redis TLS support
To enable TLS support (rediss://), set the new environment variable REDIS_SSL to true.
The day prefix for sleep start times that began before midnight
From now on, the start time of a sleep session in the sleep section will be prefixed with the previous day’s name if it was before midnight. If a sleep session ends after midnight, it will be displayed under the following day in the admin UI (as it always has been). Please note that this is dynamic and will adjust to the user’s time zone.
Support for synchronising the same data across multiple accounts
We have added dedicated support for users who need to synchronise their devices with several Open Wearables accounts for testing purposes. No configuration is required to use this feature. Simply pair the relevant accounts with your wearables account and synchronise any of them. A badge will appear in each account to indicate which accounts are linked, along with the relevant sync statuses. This shows that, for a given account, the source of the currently syncing data is a linked Open Wearables account. This means that, instead of repeatedly syncing the same data - which is virtually impossible with Garmin during backfilling, for example - it will be synced once and copied to the other accounts. More information: https://openwearables.io/docs/architecture/multi-account-sync
What's Changed
Garmin FIT files & high granularity data support
- feat(backend): add migration with new jsonb fields for workout_details by @KaliszS in #1079
- feat(backend): add average_cadence support for Garmin by @KaliszS in #1080
- feat(backend): Garmin workout samples by @KaliszS in #1082
- feat(backend): FIT files parser by @KaliszS in #1096
- fix(backend): fit map stride_length -> step_length by @KaliszS in #1097
- feat(backend): Garmin mappings for elevation, GPS & running dynamics + optional saving FIT files to s3 by @KaliszS in #1099
- fix(backend): stop storing Garmin average HR as heart_rate_min by @knowald in #1121
- feat(backend): Garmin support for workout segments (laps, swim lengths etc) by @KaliszS in #1118
- fix(backend): null workout heart_rate_min rows copied from average HR by @knowald in #1133
- feat(backend): HR & Power Zones [Garmin] by @KaliszS in #1175
Garmin Women's Health
Oura bugs
- fix: Oura missed commit and sleep summary metrics by @czajkub in #1086
- feat(backend): Oura zone_offset extraction by @KaliszS in #1168
- fix(backend): polling logic for Oura drops the current day for daily metrics by @bartmichalak in #1162
- fix(backend): store Oura HRV as RMSSD instead of SDNN by @bartmichalak and @KaliszS in #1100
Apple Health bugs
- fix(backend): widen data_source.source to VARCHAR(100) by @chrisedington in #1103
Suunto bugs
- feat(integrations): emit resting_heart_rate series from sleep HRMin by @HugoHeneault in #1055
Polar bugs
Infrastructure improvements
- fix: mount redis volume at /data so persistence works by @knowald in #1127
- feat(backend): support TLS (rediss://) for Redis connections by @kczpl in #1134
Other improvements
- fix(frontend): stop activity & sleep dates shifting a day for users west of UTC by @bartmichalak and @KaliszS in #1160
- refactor: fixed scopes db saving & improved UI by @KaliszS in #1088
- feat: Add webhook management endpoints by @czajkub in #1049
- fix: update last_synced_at on webhook receive by @czajkub in #1145
- feat: sharable syncs (the same data) between different OW users by @KaliszS in #1147
- feat(frontend): OpenWearables version visible under logout button by @KaliszS in #1165
Docs & chores
- fix(docs): update GitHub repository links in iOS SDK documentation by @bartmichalak in #1064
- docs: update available providers in readme by @bartmichalak in #1069
- chore: bump the npm-minor-patch group across 1 directory with 4 updates by @dependabot[bot] in #1057
- chore: update sentry-sdk[fastapi] requirement from >=2.59.0 to >=2.60.0 in /backend by @dependabot[bot] in #1061
- refactor(backend): update deps (redis v8) by @KaliszS in #1087
- chore: bump the npm-minor-patch group in /frontend with 13 updates by @dependabot[bot] in #1102
- docs(mcp): remove formatted duration fields absent from sleep tool output by @knowald in #1122
- docs: clarify migrations auto-run on deploy temporarily by @bartmichalak in #1129
- docs: note developer program is on hold by @bartmichalak in #1130
- docs: drop "| Open Wearables" suffix from page titles by @bartmichalak in #1131
- docs: add Fitbit and Google Health Connect to the coverage matrix by @knowald in #11...
0.5.2 - Polar refactor
Highlights
Polar got refactor & webhook support
Polar now synchronises sleep and timeseries data. By default, the Periodic Pullsync method is enabled, but webhooks can be activated in Settings > Providers.
Expanded Sport Type Support across Multi-Provider Mappings
Expanded the WorkoutType enum to explicitly support specific activities that were previously bundled into WorkoutType.OTHER. Over 70 provider-specific mappings across Apple, Garmin, Suunto, and WHOOP have been updated. New explicitly supported sports include: Cricket, lacrosse, racquetball, bowling, archery, fencing, fishing, wakeboarding, parkour, curling, and more.
Garmin fetches new fields
allDayRespiration and moveIQActivities have been added to the list of Garmin-specific attributes that we support.
Strict RMSSD enforcement for HRV-CV Resilience Score
Temporarily disabled the automatic fallback to SDNN when overnight RMSSD data is missing within the sleep window. Until SDNN-based calculations are fully validated, users without RMSSD data will now receive a null score instead of a potentially misleading fallback value.
What's Changed
- refactor(backend): updated all python dependencies by @KaliszS in #1023
- refactor(frontend): updated all TypeScript dependencies by @KaliszS in #1025
- fix(backend): garmin allDayRespiration & moveIQActivities by @KaliszS in #1026
- feat(backend): add recovery summary endpoint by @KaliszS in #1027
- chore: disable SDNN fallback in HRV-CV score calculation pending validation by @FBegiello in #1040
- fix: commit each data type individually and rollback session on exception for Oura and Whoop by @czajkub in #1022
- fix: Cast to local date for summary endpoints by @czajkub in #1033
- fix(backend): validation for date parameters in OpenAPI specs by @KaliszS in #1052
- fix(users): skip invalid user rows during email validation by @zxjt2z42px-creator in #1032
- fix: Handle duplicate EventRecordDetail inserts gracefully with savepoint by @czajkub in #1047
- refactor(frontend): update dependencies by @KaliszS in #1056
- fix: Add missing workout types, rename series type to SDK by @czajkub in #1048
- feat: Polar refactor by @czajkub in #1041
- fix(integrations): emit outgoing webhook for suunto live workout push by @HugoHeneault in #1045
New Contributors
- @zxjt2z42px-creator made their first contribution in #1032
- @HugoHeneault made their first contribution in #1045
Full Changelog: 0.5.1...0.5.2
0.5.1 - Oura & Strava Webhooks
Highlights
Oura & Strava got webhook support
By default Periodic pull only is enabled, but webhooks can be activated in Settings > Providers.
Live sync status is now available
It's already avilable in our Admin Panel (there is global Syncs tab, but all of statues are also visible in user profiles).
If you need to integrate this feature with your backend, checkout our integration guide: https://openwearables.io/docs/api-reference/guides/sync-status-stream
Sync statuses are also available as outgoing webhook events. This is the preferred solution if you need to permanently store this data, because we only store it in Redis for 24 hours.
New Admin Panel Dashboard design
RMSSD HRV is now included in Body Summary
What's Changed
- chore: bump typescript from 5.9.3 to 6.0.2 in /frontend by @dependabot[bot] in #993
- fix: add hrv rmssd to body summary by @czajkub in #949
- refactor: migrate all recovery scores from
data_point_seriestohealth_scoreby @czajkub in #950 - refactor: polish Oura webhooks and adjust them to new webhook pattern by @czajkub in #990
- refactor(frontend): Dashboard v2 by @KaliszS in #1003
- feat: sync status (fastapi SSE) by @KaliszS in #986
- feat: Sleep score recalculation after sleep session merge by @czajkub in #884
- refactor(frontend): Dashboard redesigned by @KaliszS in #1015
- fix(backend): scope x100 ratio->percent conversion to Apple provider only by @kjsteuer in #917
- refactor: Strava webhooks by @czajkub in #1010
Full Changelog: 0.5.0...0.5.1
0.5.0 - Product Hunt release!
Highlights
Product Hunt release
Important
We launched our campaign on Product Hunt today and would really appreciate your support in the form of an upvote. Thanks! https://www.producthunt.com/products/open-wearables
Webhooks Ecosystem
A full outgoing webhook infrastracture has been implemented (background processing, managment UI and other improvements since 0.4.3). We have also added more incoming webhooks support for specific providers: Suunto & Whoop. More incoming!
Health Data Precision (Sleep & Heart)
Significant effort went into refining sleep calculations (local time zone grouping, excluding awake time) and heart metrics (HRV-CV precision), greatly increasing the reliability of the analytics.
Developer Experience (DX) Evolution
Improvements of seed data generator and MCP server makes it much easier for developers to build, test and debug applications on top of Open Wearables.
What's Changed
Integrations
- feat: Add incoming webhook handlers for Whoop by @czajkub in #940
- feat: Suunto webhooks + provider capabilities reworked by @KaliszS in #942
- feat(backend): accept Health Connect exercise vocabulary by @kjsteuer in #864
- fix(backend): fall back to source.app_id / bundle_identifier for original_source_name by @kjsteuer in #863
- feat(backend): recognize Health Connect power/speed/cadence/total_calories vocabulary by @kjsteuer in #866
- fix: Oura data extraction improvements by @czajkub in #889
- chore(backend): align Strava webhook verify token with other providers by @knowald in #977
External Webhooks
- fix(backend): full sample data in timeseries webhook payloads by @KaliszS in #904
- fix: svix webhooks improvements by @KaliszS in #923
- fix: emit webhooks from bulk_create_details for SDK imports by @bartmichalak in #926
Algorithms & Data Processing
- fix(sleep): group sleep summaries by local start date instead of UTC … by @FBegiello in #852
- fix: sleep calculation overflow by @FBegiello in #855
- fix: prevent sleep duration duplication during merge and log skips by @czajkub in #842
- fix: display sleep duration correctly via endpoint by @czajkub in #883
- feat: add resilience score celery task by @czajkub in #845
- fix(backend): align health scores and workout fields with real provider formats by @FBegiello in #892
- fix: sleep duration excludes awake time, add time in bed column by @bartmichalak in #929
- fix: align sleep score date to wake-up date via direct record linkage by @FBegiello in #900
- fix: save sleep efficiency correctly for Apple and Garmin by @czajkub in #944
- fix: improve HRV-CV storage precision and add 0-100 scoring by @FBegiello in #919
- fix: improve HRV-CV storage precision and add 0-100 scoring (retry) by @bartmichalak in #958
- fix(backend): correct peak_expiratory_flow_rate unit to L/min by @bartmichalak in #979
Dashboard & User Experience
- feat: add per-user data summary endpoint and dashboard display by @bartmichalak in #839
- feat(frontend): display API base URL in credentials tab by @bartmichalak in #867
- feat(dashboard): add Oura as a seed data provider by @FBegiello in #887
- feat(frontend): add webhooks management UI by @bartmichalak in #902
- feat(frontend): mark webhooks as beta by @bartmichalak in #918
- fix(frontend): improved webhook deliveries tab by @KaliszS in #925
- fix: seed time-series UX follow-ups by @bartmichalak in #932
- feat: delete workout/sleep endpoints + UI by @KaliszS in #952
- fix(frontend): silence settings route tree warnings by @bartmichalak in #955
- refactor(frontend): provider live sync mode badges by @KaliszS in #964
- refactor(frontend): remove redundant webhook manage button by @DominikCywinski in #965
- feat(frontend): add API key update functionality and UI support by @skadzielawa in #978
- fix(backend): hide non implemented webhooks in UI by @KaliszS in #960
Documentation
- docs: fix e2e guide pagination field + provider list endpoint by @farce1 in #886
- fix(integrations): subscribe the Celery worker to the webhook_sync queue by @srirsiva in #962
- docs: trim descriptions to ≤155 chars and separate display title from og:title by @blackbudda in #888
- docs: add Apple Health integrations list by @bartmichalak in #890
- docs: document grace-period flag for historical sync on connect by @bartmichalak in #897
- docs: add Seed Data Generator dev guide by @bartmichalak in #903
- docs: refresh backend e2e integration guide by @bartmichalak in #909
- docs: add Health Scores documentation by @anjazych in #908
- docs: note cloud_only flag for web integration flows by @bartmichalak in #922
- docs: fix typo "envionment" in Makefile help text by @knowald in #967
- docs: fix Whoop Developer Dashboard link by @bartmichalak in #982
Infrastructure, Backend & Chores
- perf(backend): offload webhook processing to background tasks by @sLeeNguyen in #685
- refactor(backend): move historical sync dispatch into provider strategy by @bartmichalak in #817
- chore: update sentry-sdk[fastapi] requirement from >=2.42.1 to >=2.57.0 in /backend by @dependabot[bot] in #872
- feat(mcp): add get_timeseries tool for granular sample queries by @kjsteuer in #882
- chore: bump jsdom from 28.1.0 to 29.0.0 in /frontend by @dependabot[bot] in #869
- chore: bump the npm-minor-patch group across 1 directory with 15 updates by @dependabot[bot] in #898
- chore: bump vite from 7.3.1 to 8.0.0 in /frontend by @dependabot[bot] in #870
- fix: svix-server railway deployment by @KaliszS in #891
- chore: deprecate external_user_id in OpenAPI schema by @bartmichalak in #905
- chore(dashboard): remove legacy seed_data_service module by @FBegiello in #912
- chore: remove unfinished Python SDK by @bartmichalak in #906
- fix: slow tests after adding lifespan with webhook event registration by @KaliszS in #924
- feat: continuous time-series in seed generator by @bartmichalak in #931
- chore: bump the npm-minor-patch group in /frontend with 5 updates by @dependabot[bot] in #913
- chore: bump @vitejs/plugin-react from 5.2.0 to 6.0.1 in /frontend by @dependabot[bot] in #914
- fix(backend): move faker to dependencies group by @KaliszS in #937
- feat(backend): add trace_id & user_id to logs by @KaliszS in #941
- chore: bump the npm-minor-patch group in /frontend with 6 updates by @dependabot[bot] in #946
- chore: optimize backend docker build caching and startup time...
0.4.3
Highlights
Grace-period feature flag for automatic historical sync on provider connect
A HISTORICAL_SYNC_ON_CONNECT flag has been added, which is set to True by default and ensures that historical data is synchronised when pairing devices with Open Wearables (as was the case prior to version 0.4.2). In about two months’ time, we will remove it, and from then on, syncing historical data will require pressing a separate button by default in our UI portal or by accessing separate endpoint /api/v1/providers/{provider}/users/{user_id}/sync/historical when you integrate your backend. You can switch to this functionality now by setting this variable in .env to False, which we recommend.
Open Wearables Health Scores (Sleep & Resilience)
Warning
Our scores are still in beta, so there may be bugs & the algorithms may change
We are officially introducing our own proprietary scores! This release brings the new Sleep Score and Resilience Score, calculated directly by Open Wearables. To support this, we've added background Celery tasks that automatically calculate daily sleep scores for your users. Huge thanks to @anjazych, @FBegiello and @czajkub for driving these new composite metrics.
Outgoing Webhooks
Warning
This is also beta functionality, so bugs may still occur.
By popular demand, we've added support for outgoing (external) webhooks. Your application can now listen to events and data streams from Open Wearables in real-time, greatly improving sync architecture and reducing the need for polling. Full documentation for this feature is now available here: https://openwearables.io/docs/api-reference/guides/webhooks
Dashboard & Observability Upgrades
We've made several quality-of-life improvements for developers. You'll find a new SDK logs endpoint to help with sync observability, and a handy "seed data generator" built straight into the dashboard settings to speed up your testing and development workflows.
What's Changed
Integrations
- feat: Save health scores from Garmin and Whoop by @czajkub in #818
- fix: resolve source name correctly for workouts/sleep by @czajkub in #815
API & Backend
- feat: introduce Sleep Score by Open Wearables by @FBegiello in #787
- feat: introduce Resilience Score by Open Wearables by @FBegiello in #803
- feat: add celery tasks for calculating daily sleep scores by @czajkub in #826
- feat(backend): outgoing (external) webhooks by @KaliszS in #821
- feat: add SDK logs endpoint for sync observability by @bartmichalak in #819
- feat(backend): grace-period feature flag for automatic historical sync by @KaliszS in #850
Frontend & Dashboard
- feat: add Health Scores tab to user detail page by @bartmichalak in #828
- feat: add seed data generator to dashboard settings by @bartmichalak in #806
- fix: make save button a floating bar in provider settings by @bartmichalak in #833
- fix: hide sync buttons for SDK-based providers by @bartmichalak in #825
- fix(frontend): set NODE_ENV=production in Docker build by @bartmichalak in #820
Docs & SEO
- feat(docs): outgoing webhooks by @KaliszS in #846
- feat(seo): trigger LP sitemap sync on docs change by @piotrpasierbek in #823
- docs: add SEO frontmatter to OW documentation pages by @blackbudda in #822
- fix(docs): add missing sidebarTitle to docs pages with SEO titles by @bartmichalak in #832
- docs: hide vendor workout endpoints from public docs by @bartmichalak in #834
- docs: rename Authorize Provider to Get Provider Authorization URL by @bartmichalak in #836
- docs: fix localhost links in API reference by @bartmichalak in #837
- docs: clarify that icon_url is a relative URL by @bartmichalak in #838
Chores
- chore: add prettier format check to pre-commit hooks by @bartmichalak in #829
- chore: remove stale github workflow by @bartmichalak in #848
- chore: regenerate routeTree.gen.ts with current router plugin by @bartmichalak in #849
New Contributors
- @piotrpasierbek made their first contribution in #823
- @blackbudda made their first contribution in #822
Full Changelog: 0.4.2...0.4.3
0.4.2
Highlights
Garmin webhooks & historical sync
Garmin has been updated to work with the incoming webhook streaming architecture introduced in v0.4.1. Live sync now uses webhooks as the priority path, and there's a new historical sync option in the UI - backfill up to 365 days of data. Connection cards in the portal now show provider-specific historical limits and a history sync action. Thanks to @KaliszS for driving this one 🎉
Breaking change: Historical data no longer syncs automatically on provider connection. Users now trigger historical sync explicitly through the UI.
More: Webhook Handler
Health Scores API
New GET /users/{user_id}/health-scores endpoint by @czajkub. Supports filtering by category, provider, data source, and date range, plus pagination and timeseries metadata. There's also a bulk-create path for ingesting scores from providers. This lays the groundwork for surfacing composite health metrics in the app.
Cleaner API Reference
The Mintlify API docs got a major cleanup - endpoints are now tagged as External, Internal, or System, and the API Reference tab only shows the public-facing ones. No more scrolling past webhook receivers and debug routes to find the endpoints you actually need.
Redis memory leak fix
Celery's pidbox reply keys were accumulating in Redis indefinitely. Added TTL and expiration (300s) to control queues so Redis cleans them up automatically. If you've noticed Redis memory creeping up over time, this should take care of it.
What's Changed
Integrations
- feat: Garmin incoming webhooks & UI portal changes by @KaliszS in #764
- fix: handle lowercase pulseox webhook payloads by @saschabuehrle in #766
- fix(backend): skip push-only providers in sync_vendor_data task by @haoyu-haoyu in #774
- refactor(backend): remove Auto Health Export connector endpoint and all related logic by @bartmichalak in #779
- chore: add read:body_measurement scope to Whoop config in .env.example by @bartmichalak in #811
API & Backend
- feat: add health score model by @czajkub in #793
- feat(backend): add service and endpoint for health scores by @czajkub in #804
- refactor(backend): categorize API endpoints and curate docs API reference by @bartmichalak in #778
- fix: set TTL on Celery pidbox reply keys to prevent Redis memory leak by @bartmichalak in #559
- refactor(backend): sync data router by @KaliszS in #812
Docs
Frontend
- chore: bump the npm-minor-patch group in /frontend with 10 updates by @dependabot[bot] in #809
New Contributors
- @saschabuehrle made their first contribution in #766
- @haoyu-haoyu made their first contribution in #774
Full Changelog: 0.4.1...0.4.2

