Key Takeaways
- Withings joins as a new provider, with the body, activity, sleep, and workout data categories available straight away.
- Historical sync status moved from Redis, which kept it for 24 hours, into the database. A new sync history API records when a long historical sync actually finished and where it stopped if it dropped, so it can be resumed from that range. Live syncs are deliberately not recorded.
- Data ingestion is roughly 3.7 times faster on large batches after a rewrite of the bulk upsert path, and Apple Health XML uploads now handle files up to 5 GiB.
- Four account takeover vulnerabilities are closed, all of them backwards-compatible. API keys are stored hashed, password changes revoke refresh tokens, developer records are restricted to their own account, and Garmin webhooks verify the client ID header.
- Apple Health imports now run identically against AWS S3 and self-hosted MinIO, which means a fully local deployment with no AWS dependency can ingest multi-gigabyte exports.
Is Your HealthTech Product Built for Success in Digital Health?
.avif)
Open Wearables, our open-source platform for health intelligence, just shipped version 0.8.0. Withings joins as a new provider, and alongside it this release concentrates on what happens after the data starts flowing: knowing whether a sync worked, moving large volumes without falling over, and closing the gaps that matter when a platform holds real users' health data.
Withings is the headline addition. The parts that will change day-to-day operations are underneath it.
Withings: a new provider, fully wired from day one
Withings is now a supported provider in Open Wearables. Body, activity, sleep, and workout data categories are all available straight away, using the same normalized data model as every other source.
Withings covers a device category that wrist-worn trackers do not. Body composition from connected scales, blood pressure and heart rate, blood glucose, blood oxygen, and body temperature all come through the same integration, alongside daily activity, sleep, and workouts. For teams building around weight management or cardiometabolic programs, that is data a fitness band does not report.
Two things to know before you plan a rollout. Sleep stage timelines, ECG, and HRV are not supported for Withings. And syncing runs by polling the Withings APIs by default: webhook push is optional, and because Withings creates notification subscriptions per user rather than once per application, an app without compliant webhooks is capped by Withings at 10 linked users. Full setup detail is in the provider documentation.
Historical syncs now leave a trace
Sync completion status was already being tracked, but it lived in Redis, and Redis keeps it for 24 hours. Anything older was gone. 0.8.0 moves that state into the database so it persists.
Three problems drove this, and all three are specific to historical syncs.
Redis was not durable. A 24-hour window is fine for checking whether last night's sync worked. It is useless for understanding what happened to a user's backfill a week ago.
Long historical syncs had no completion signal. Historical syncs pull large ranges, often a full year. On Apple that can take a very long time. The Redis status arrived when the sync started, not when it finished, so there was no way to tell a sync still running from one that died. The database now records when a historical sync actually ended.
Dropped syncs could not be resumed precisely. When a historical sync covering a year of data broke partway through, for example on a connection drop, there was no record of how far it had got. Restarting meant guessing. The per-data-type breakdown now shows where the run stopped, so it can be continued from that range rather than restarted from scratch.
0.8.0 introduces a sync run record capturing the provider, the scope, the status, timing, how many items came back, and any errors encountered. A companion record gives one row per data type per run, so a sync that pulled sleep successfully but failed on workouts shows exactly that instead of an opaque blob.
Live syncs are deliberately not written to the table. The data model supports them, but recording every live sync would flood it. Historical syncs are where the durability and resumability actually matter.
Runs that stop reporting are closed by a stale sweep, which checks Redis for liveness first so that a slow backfill is not cut off prematurely.
Two endpoints expose the data:
GET /users/{id}/sync/history
GET /sync/history/{run_key}The data is available through the API in this release. Surfacing it in the admin panel is planned for the next frontend iteration.
Ingestion got substantially faster
The data point series bulk upsert was rewritten. The previous implementation issued chunked INSERT statements carrying an enormous number of bind parameters. A batch is now copied into a temporary staging table and merged into the target with a single statement.
Locally, that is a 3.7x speedup on a 50,000-row batch: 6.10 seconds down to 1.67.
Why it matters in practice: health data arrives in bursts. A user connects a new device and backfills months of history. A cohort syncs overnight within the same window. An Apple Health export lands with years of samples in one file. Ingestion throughput determines whether those bursts clear quickly or queue up behind each other, and queued ingestion means users looking at stale dashboards.
XML imports also moved to their own dedicated Celery queue, so a large Apple export no longer competes with routine provider syncs for worker capacity.
Multi-gigabyte Apple Health exports
The Apple Health XML upload limit moved from 1 GiB to 5 GiB, with a progress dialog on the frontend so large uploads are visible rather than appearing to hang.
Apple Health exports from long-term users are genuinely large, and users with years of accumulated history are often exactly the ones worth onboarding, because they bring that history with them. The old limit rejected those exports outright.
The flow now works identically against AWS S3 and 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, which matters for any organization with a policy against third-party cloud storage.
Security hardening
Four ways to take over an account or a deployment are now closed, all of them backwards-compatible, so upgrading does not require changes to your integration.
API keys are stored hashed. 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 revokes every refresh token the developer holds. Refresh tokens have no expiry, so previously a leaked token worked forever, including after the password was changed in response to the leak.
The developer patch endpoint is restricted to the caller's own account. Before this fix, any team member could set another developer's password.
Garmin webhooks compare the client ID header against the configured value, instead of only checking that the header is present.
Better provenance and richer workout data
Three providers were already sending per-workout provenance and the platform was dropping it. Workouts now carry entry source, intensity, and label fields, so you can 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. Whether a workout was measured or typed in by hand changes how much weight it should carry in anything built on top.
Oura's intraday MET series is new data, not a change to anything you already have. Oura sends this series alongside daily activity, and the platform was not storing it at all. It is now parsed into individual timestamped samples at the provider's own interval and stored as physical effort, giving a continuous picture of how intense someone's movement was across the day.
Nothing about existing Oura data changes, so there is no daily-to-intraday migration to plan for. This is an additional series that starts appearing after you upgrade. Oura is the only provider exposing MET at this granularity today, so it is currently Oura-only.
The Ultrahuman integration now parses granular sleep stages from the sleep graph and stores skin temperature as a proper series. Suunto gained pause-aware stop times, recovered sleep windows that were previously dropped, awake time, and SpO2Max and average HRV from sleep sessions. Whoop's HRV is now split into RMSSD and SDNN as distinct metrics, with the recovery score field deprecated in the recovery summaries endpoint. Whoop also gained cycle endpoint support and a fix to the recovery endpoint.
Running it: Docker images and deployment
Official Docker images are the recommended deployment path:
- Backend: themomentum/open-wearables-backend
- Frontend: themomentum/open-wearables-frontend
Both now publish for linux/amd64 and linux/arm64 under the same tags as before, so ARM hosts pull a native image.
Tags follow a predictable scheme. The latest tag tracks the newest stable release, 0.8.0 and 0.8 pin to this one, and the nightly tag is unstable but always up to date with main, deployed every night. For production, pin a version.
The production Docker Compose setup has been replaced by a proper deployment guide, and provider and webhook documentation has been refreshed with deployment guidance.
Upgrading
Standard Docker Compose update. Database migrations run automatically on startup.
Two things to check before you upgrade:
If you track the nightly tag and updated between 2026-09-07T17:46:43Z and 2026-09-08T14:55:55Z, you may have pulled a version containing a faulty migration. Verify that your workout details table contains the entry source, intensity, and label columns. If they are missing, add them manually:
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);If you use the Garmin backfill controls, the cancel and retry endpoints are removed in this release and the status endpoint is deprecated. Garmin allows one backfill request per timeframe per type and answers 409 afterwards, so retry could never do anything useful. The per-type timeout and lock TTL cover what those controls were handling, and the periodic garbage collection task is gone as a result.
The full changelog is at github.com/the-momentum/open-wearables/releases/tag/0.8.0.
Community
Open Wearables is approaching 2,500 stars on GitHub, with 485 forks and 59 contributors to date. Six contributors made their first contribution in this release.
That last number is the one worth watching. Provider integrations, bug fixes, and documentation improvements increasingly arrive from people running the platform in their own products.
The project is at github.com/the-momentum/open-wearables. The Discord community is the fastest place to get an answer.






