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
Note
We have changed the way we tag images:
latest- this is how you can get the newest stable image0.8.0,0.8- this is how you can get the chosen stable releasenightly- unstable, but always up to date with main (we deploy this tag every night)
Caution
A warning for nightly users regarding instability (migration #9b079...): If you have already used the nightly tag and carried out an update between 2026-09-07T17:46:43Z and 2026-09-08T14:55:55Z, please check whether your database contains the columns entry_source, intensity and label in the workout_details table. If not, this means you downloaded a version containing a faulty migration, in which case we recommend adding these columns manually using SQL:
ALTER TABLE workout_details
ADD COLUMN IF NOT EXISTS entry_source VARCHAR(32),
ADD COLUMN IF NOT EXISTS intensity VARCHAR(10),
ADD COLUMN IF NOT EXISTS label VARCHAR(255);Withings integration
We now have integration with a new provider, Withings! This integration introduces new elements to our global architecture. We are making all data categories available straight away, along with the methods for integrating them. Thank you to our contributor for preparing the pull request!
Historical Sync metadata preserved
Every sync now leaves a trace. We introduced a sync_run table that tracks a complete synchronization run - provider, scope (historical or live), status, timing, item counts and errors - plus a per-data-type breakdown within another table sync_run_data_type, so you get one row per data type per run instead of an opaque blob. Runs are tracked across all ingestion paths: provider pulls & pushes, SDK uploads, SDK logs, Apple XML imports and Garmin backfills. A stale sweep closes runs that stopped reporting, checking Redis for liveness first so a slow backfill isn't cut off prematurely. Two new endpoints expose it: GET /users/{id}/sync/history and GET /sync/history/{run_key}. It will be also added to our frontend in its next iteration.
SDK uploads queue a reference instead of the payload
Passing the full SDK upload body as a Celery task argument serialized it into the Redis broker queue, so every queued message carried several megabytes and a backlog grew in proportion to payload size. When new .env SDK_PAYLOAD_S3_OFFLOAD is enabled, the sync endpoint now enqueues only an S3 reference and the worker loads the body when the task runs, keeping each broker message at roughly 1 KB. The payload was already being written to S3, so this removes a duplicate copy rather than adding a write.
Note
New SDK_PAYLOAD_S3_OFFLOAD variable is off by default, and nothing changes for deployments that leave it that way.
Workouts now tell you where they came from
Three providers were already sending per-workout provenance and we were dropping it on the floor. Workouts now carry entry_source, intensity and label, so you can finally tell a measured workout from a manually entered one, and see the name the user gave it. Oura, Strava, Garmin and Apple all populate these fields.
Oura MET - movement dynamics throughout the day
Oura sends an intraday MET (metabolic equivalent of task) series alongside daily activity. We now expand that series into individual timestamped samples at the provider's own interval and store it as physical effort, so you get a continuous picture of how intense someone's movement was across the day - not just a daily total. It's also surfaced on the workouts API. Oura is the only provider exposing MET at this granularity today, so this is currently Oura-only.
Multi-gigabyte Apple Health exports, on any storage backend
The Apple Health XML upload limit went from 1 GiB to 5 GiB, with a progress dialog on the frontend. Just as importantly, the flow now works identically against AWS S3 and a self-hosted MinIO - previously the XML path used a hard-coded AWS client and depended on SNS, which MinIO cannot emit. A fully local, AWS-free deployment can now ingest multi-gigabyte Apple exports through the portal.
Security hardening
Four ways to take over an account or a deployment are now closed, all backwards-compatible. API keys are stored hashed and the raw value is shown only once - on create or rotate; the migration hashes existing keys in place, so current integrations keep working. Changing a password now revokes every refresh token the developer holds - previously a leaked token worked forever, since refresh tokens have no expiry. PATCH /developers/{id} is restricted to the caller's own account; before, any team member could set another developer's password. And Garmin webhooks now compare the garmin-client-id header against the configured value instead of just checking that it's present.
Ingestion got substantially faster
We rewrote the DataPointSeries bulk upsert. Instead of chunked INSERT ... VALUES statements carrying an enormous number of bind parameters, a batch is now COPY-ed into a temporary staging table and merged into the target with a single statement. Locally that's a 3.7× speedup on a 50,000-row batch (6.10s → 1.67s). XML imports got their own dedicated Celery queue so they stop competing with everything else.
Multi-arch Docker images
Backend and frontend images now publish for both linux/amd64 and linux/arm64 under the same tags as before.
Garmin backfill controls are going away - ⚠️ deprecation
The Garmin backfill cancel and retry endpoints are removed, and GET status is now deprecated - please stop using it; it will be removed in a future release. This whole mechanism dates back to before we understood how Garmin's backfill API behaves, and it never fit the way other integrations handle historical sync: Garmin allows one request per timeframe per type and answers 409 afterwards, so retry could never do anything useful. The periodic GC task is gone too - it accounted for roughly 15% of worker log lines and in practice never cleared anything, since the per-type timeout and the lock TTL already cover those cases.
What's Changed
Platform
- fix: resolve race condition in token refresh using distributed lock by @sehmbimanvir in #1440
- feat(backend): multipart upload + MinIO parity for Apple's XML imports by @MichalZak17 in #1445
- feat(backend): offload SDK upload payload to S3, enqueue only a reference by @bartmichalak & @KaliszS in #1460
- perf(backend): rewrite DataPointSeries bulk upsert to use COPY + staging merge by @McArthur955 in #1492
- ci: publish multi-arch images via native arm64 runners by @knowald in #1584
Data model
- feat(backend): historical sync status storage by @KaliszS in #1439
- feat:
SyncRuntracking - follow-up by @czajkub in #1448 - feat(backend): add name, intensity and recording source to the workouts API by @McArthur955 in #1510
Security
- fix(auth): revoke developer refresh tokens on password change by @bartmichalak in #1590
- fix(auth): restrict developer update by ID to the caller's own account by @bartmichalak in #1599
- fix(auth): store API keys hashed and show the raw key only once by @bartmichalak in #1592
Scores
Withings
- feat: Withings integration (body, activity, sleep, workouts) - rewrite of #1177 by @bwickner & @czajkub in #1538
Suunto
- feat(integrations): pause-aware stopTime + gear from SummaryExtension by @HugoHeneault in #1043
- fix(integrations): recover missing Suunto sleep windows safely by @bwickner in #1187
- fix(backend): fetch Suunto awake time by @KaliszS in #1525
- feat(backend): Spo2Max & AvgHRV from Suunto sleep by @KaliszS in #1528
Apple
- feat(backend): raise upload limit to 5 GiB and add upload progress dialog for uploading Apple's XML by @MichalZak17 in #1444
Whoop
- fix: Update Whoop workout type mappings by @czajkub in #1451
- fix: split HRV into RMSSD/SDNN and deprecate recovery_score in recovery summaries endpoint by @LouisFerger-Andrews in #1452
- fix(backend): fix recovery endpoint and resolve cycle_id for recovery.updated by @sehmbimanvir in #1464
- feat: Whoop
/v2/cycleendpoint support by @czajkub in #1462 - fix(integrations): map additional WHOOP workout sport names by @Panda-Lewandowski in #1504
Oura
- fix(backend): remove invalid 'activity' OAuth scope from default by @bartmichalak in #1421
- feat(backend): add parsed Oura met data to the workouts API by @McArthur955 in #1524
Ultrahuman
- fix(backend): report real synced item counts for Ultrahuman by @bartmichalak in #1469
- fix: store Ultrahuman skin temperature under skin_temperature series by @bartmichalak in #1473
- feat: parse Ultrahuman granular sleep stages from sleep_graph by @bartmichalak in #1476
Garmin
- fix(backend): verify webhook garmin-client-id against configured value by @bartmichalak in #1507
- refactor(backend): drop backfill GC task, cancel and retry endpoints for Garmin by @bartmichalak in #1591
SDK connection
- feat: Emit
connection.createdandconnection.revokedevents for SDK by @czajkub in #1423 - feat(auth): let SDK tokens disconnect their own SDK connections by @KaliszS in #1601
Outgoing webhooks
- feat: Emit
connection.createdandconnection.revokedevents for SDK by @czajkub in #1423 - fix(backend): unify sleep data delivered by api & webhook by @McArthur955 in #1540
- fix(backend): send webhook after seeding the sleep data by @McArthur955 in #1555
- fix(backend): allow removing webhook event filters by @McArthur955 in #1578
Other
- fix: remove
handle_duplicatesdecorator fromevent_record_detail_service'screateby @czajkub in #1406 - fix(frontend): clear "Device info not available" label when a source has no device details by @MichalZak17 in #1479
- feat(mcp): add get_menstrual_cycles tool by @adam8113 in #1463
- feat: add metric descriptions with tooltip hints to coverage matrix by @bartmichalak in #1511
- feat(frontend): make sidebar responsive on mobile by @fr3nch13 in #1416
- fix(frontend): tanstack menu fixed position by @KaliszS in #1536
- refactor(backend): improved GET /users endpoint by @KaliszS in #1557
- fix(backend): release the DB connection before an SSE stream starts by @KaliszS in #1565
Docs & chores
- docs: replace the prod compose setup with a Docker deployment guide by @knowald in #1429
- chore: Remove old data migrations ran during startup by @czajkub in #1455
- docs: refresh providers, webhooks, and add deployment question by @bartmichalak in #1459
- chore: bump the npm-minor-patch group in /frontend with 4 updates by @dependabot[bot] in #1457
- chore(mcp): bump the python-minor-patch group across 1 directory with 7 updates by @dependabot[bot] in #1441
- docs: trim the docs - move Data Migrations into Developer Guides by @bartmichalak in #1475
- docs: document structured logging as the default in backend/AGENTS.md by @bartmichalak in #1481
- chore: add SECURITY.md vulnerability reporting policy by @bartmichalak in #1482
- chore: nightly Docker builds and release-driven tag scheme by @bartmichalak in #1480
- chore: update sentry-sdk[fastapi] requirement from >=2.66.1 to >=2.68.0 in /backend by @dependabot[bot] in #1490
- chore: bump the npm-minor-patch group in /frontend with 12 updates by @dependabot[bot] in #1487
- chore: bump the python-minor-patch group across 2 directories with 5 updates by @dependabot[bot] in #1488
- chore(mcp): upgrade FastMCP 2 to 3 by @bartmichalak in #1505
- docs: rename "Sync SDK" to "Mobile SDK" by @bartmichalak in #1506
- docs: document Samsung Health production requirements by @bartmichalak in #1509
- docs: remove Garmin Data Pipeline page by @bartmichalak in #1513
- chore: update sentry-sdk[fastapi] requirement from >=2.68.0 to >=2.68.1 in /backend by @dependabot[bot] in #1534
- chore: bump the npm-minor-patch group in /frontend with 11 updates by @dependabot[bot] in #1532
- chore: bump the python-minor-patch group across 2 directories with 8 updates by @dependabot[bot] in #1533
- docs: rewrite contributing guidelines and PR template by @bartmichalak in #1546
- docs: broaden redaction guidance beyond tokens and health data by @bartmichalak in #1551
- docs: point Getting Help at the published docs and community channels by @bartmichalak in #1552
- chore: cleanup app startup script by @czajkub in #1553
- docs: clarify that the admin seed only runs on an empty developer table by @bartmichalak in #1564
- docs: add Historical Data Availability page for providers by @bartmichalak in #1512
- chore: bump the npm-minor-patch group in /frontend with 15 updates by @dependabot[bot] in #1593
- chore: bump the python-minor-patch group across 2 directories with 9 updates by @dependabot[bot] in #1594
- docs: document developer portal settings and the access model by @bartmichalak in #1595
- chore(mcp): upgrade dependencies by @bartmichalak in #1600
- ci(backend): guard the migration chain against re-pointed migrations by @bartmichalak in #1602
- chore: update Open Wearables to 0.8 by @KaliszS in #1604
New Contributors
- @sehmbimanvir made their first contribution in #1440
- @LouisFerger-Andrews made their first contribution in #1452
- @adam8113 made their first contribution in #1463
- @McArthur955 made their first contribution in #1492
- @fr3nch13 made their first contribution in #1416
- @Panda-Lewandowski made their first contribution in #1504
Full Changelog: 0.7.0...0.8.0