Integrating Withings with Lovable uses Edge Functions to manage OAuth2 authorization and receive data push notifications from connected devices like smart scales, blood pressure monitors, and sleep trackers. Store Withings OAuth credentials in Cloud Secrets, implement the authorization flow and subscription-based webhooks, and build health metric dashboards showing body weight, blood pressure, and sleep trends. Setup takes 45 minutes.
Why integrate Withings with Lovable?
Withings occupies a unique position in the health tech market: its devices are designed with clinical-grade accuracy rather than just consumer-grade wellness tracking. The Withings Body+ scale measures weight, body fat percentage, muscle mass, bone density, and water percentage using bioelectrical impedance. The BPM Connect blood pressure monitor is clinically validated and syncs systolic, diastolic, and heart rate readings. The Sleep Analyzer (a under-mattress sensor, no wearable required) captures sleep stages, breathing disturbances, and heart rate variability with research-grade accuracy.
This medical-grade data makes Withings the preferred integration for health management apps targeting users managing chronic conditions, clinicians monitoring patients remotely, or wellness programs that need defensible health metrics rather than just activity estimates. If your Lovable app serves a health-conscious audience or operates in a clinical context, Withings provides the data accuracy that consumer wearables often lack.
Withings' API uses a subscription model for real-time data delivery — you register a webhook endpoint with Withings, and they notify you whenever a user takes a measurement with any connected device. You then call the Measure API to fetch the specific measurements. For historical data, a direct pull API retrieves measurements by date range and measurement type. The combination of push notifications (for real-time updates) and pull APIs (for historical queries) gives complete access to a user's entire health dataset.
Integration method
Withings has no native Lovable connector. Integration requires Supabase Edge Functions to manage Withings' OAuth2 authorization code flow, create subscriptions so Withings pushes new measurements to your webhook endpoint, and fetch historical measurement data via the Measure API. Credentials are stored in Cloud Secrets. Withings uses a push model for new measurements via subscriptions but also supports pull-based historical data fetching.
Prerequisites
- A Lovable project with Cloud enabled and a deployed URL for webhooks
- A Withings developer account — register at developer.withings.com
- A Withings application registered to obtain OAuth2 client_id and client_secret
- A Withings account linked to at least one device for testing (or use the Withings Health API demo account)
- Understanding of OAuth2 authorization code flow
Step-by-step guide
Register a Withings application and configure OAuth2
Register a Withings application and configure OAuth2
Go to developer.withings.com and create a developer account. Click 'My Applications' → 'Create an application'. Fill in your application name, description, and select the data categories you need: Body measurements, Heart rate, Sleep, Activity — select all that apply to your app's features. Set the Callback URL to your app's OAuth callback path: https://your-app.lovable.app/auth/withings/callback. For the webhook URL, use your Edge Function URL: https://your-project.supabase.co/functions/v1/withings-webhook (you'll create this Edge Function next). After creation, Withings provides a Client ID and Consumer Secret (Withings uses 'consumer_secret' rather than 'client_secret' in their documentation, but it's the same concept). Copy both. The Withings authorization URL is https://account.withings.com/oauth2_user/authorize2 and the token endpoint is https://wbsapi.withings.net/v2/oauth2. Withings access tokens expire after 3 hours and refresh tokens expire after 12 months. The 12-month refresh token expiry is generous — users who use your app at least monthly will never need to re-authorize. Implement proactive token refresh when the access token is within 15 minutes of expiry.
Pro tip: Withings has a demo mode in their developer portal where you can generate test measurements without a physical device. Use this to test your webhook and measurement fetching logic before requiring users to have actual Withings devices.
Expected result: Your Withings application is registered with Client ID and Consumer Secret saved. Callback URL and webhook URL are configured.
Store Withings credentials and implement OAuth flow
Store Withings credentials and implement OAuth flow
Open your Lovable project, click '+', select 'Cloud', and expand Secrets. Add WITHINGS_CLIENT_ID, WITHINGS_CONSUMER_SECRET, WITHINGS_REDIRECT_URI, WITHINGS_API_BASE ('https://wbsapi.withings.net'), and WITHINGS_AUTH_URL ('https://account.withings.com/oauth2_user/authorize2'). Withings OAuth2 follows the standard authorization code flow. Build a 'Connect Withings' button that constructs the authorization URL with client_id, redirect_uri, response_type=code, scope (user.info,user.metrics,user.activity,user.sleepevents), and state. After the user authorizes, Withings redirects to your callback URL with the code. The token exchange is a POST to https://wbsapi.withings.net/v2/oauth2 with action=requesttoken, grant_type=authorization_code, client_id, consumer_secret, code, and redirect_uri. Withings uses 'consumer_secret' (not 'client_secret') in their API and the action parameter rather than standard OAuth2 field names — the response structure is also wrapped in a Withings-specific envelope. Withings' token response uses a custom format: { status: 0, body: { userid, access_token, refresh_token, expires_in, ... } }. Status 0 indicates success; non-zero indicates an error. Always check the status field, not just the HTTP status code.
Create a Supabase Edge Function at supabase/functions/withings-oauth/index.ts. Handle: action='exchange' with code and user_id — POST to Withings token endpoint with action=requesttoken, store access_token, refresh_token, expires_at, withings_user_id in withings_tokens table; action='get-token' with user_id — return valid access_token (auto-refresh using action=refreshtoken if expiring in 15 minutes); action='create-subscription' with user_id — POST to Withings notify/subscribe endpoint to register webhook for body, activity, and sleep measurement types.
Paste this in Lovable chat
1// supabase/functions/withings-oauth/index.ts2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";3import { createClient } from "https://esm.sh/@supabase/supabase-js@2";45const CLIENT_ID = Deno.env.get("WITHINGS_CLIENT_ID") ?? "";6const CONSUMER_SECRET = Deno.env.get("WITHINGS_CONSUMER_SECRET") ?? "";7const REDIRECT_URI = Deno.env.get("WITHINGS_REDIRECT_URI") ?? "";8const API_BASE = Deno.env.get("WITHINGS_API_BASE") ?? "https://wbsapi.withings.net";9const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" };1011serve(async (req) => {12 if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });13 const supabase = createClient(Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "");14 const body = await req.json();1516 try {17 if (body.action === "exchange") {18 const { code, user_id } = body;19 const res = await fetch(`${API_BASE}/v2/oauth2`, {20 method: "POST",21 headers: { "Content-Type": "application/x-www-form-urlencoded" },22 body: new URLSearchParams({ action: "requesttoken", grant_type: "authorization_code", client_id: CLIENT_ID, consumer_secret: CONSUMER_SECRET, code, redirect_uri: REDIRECT_URI }),23 });24 const data = await res.json();25 if (data.status !== 0) throw new Error(`Withings error ${data.status}: ${data.error}`);26 const { access_token, refresh_token, expires_in, userid } = data.body;27 const expiresAt = new Date(Date.now() + expires_in * 1000).toISOString();28 await supabase.from("withings_tokens").upsert({ user_id, access_token, refresh_token, expires_at: expiresAt, withings_user_id: userid }, { onConflict: "user_id" });29 return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });30 }3132 if (body.action === "get-token") {33 const { user_id } = body;34 const { data: token } = await supabase.from("withings_tokens").select("*").eq("user_id", user_id).single();35 if (!token) throw new Error("No Withings connection found.");36 if (new Date(token.expires_at) < new Date(Date.now() + 900_000)) {37 const res = await fetch(`${API_BASE}/v2/oauth2`, {38 method: "POST",39 headers: { "Content-Type": "application/x-www-form-urlencoded" },40 body: new URLSearchParams({ action: "refreshtoken", client_id: CLIENT_ID, consumer_secret: CONSUMER_SECRET, refresh_token: token.refresh_token }),41 });42 const data = await res.json();43 if (data.status !== 0) throw new Error(`Token refresh failed: ${data.error}`);44 const expiresAt = new Date(Date.now() + data.body.expires_in * 1000).toISOString();45 await supabase.from("withings_tokens").update({ access_token: data.body.access_token, refresh_token: data.body.refresh_token, expires_at: expiresAt }).eq("user_id", user_id);46 return new Response(JSON.stringify({ access_token: data.body.access_token }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });47 }48 return new Response(JSON.stringify({ access_token: token.access_token }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });49 }5051 return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: corsHeaders });52 } catch (err) {53 return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });54 }55});Pro tip: After exchanging the authorization code for tokens, immediately call the create-subscription action to register your webhook for new measurements. Without a subscription, Withings won't push measurement notifications to your app — users would need to manually trigger data syncs.
Expected result: The withings-oauth Edge Function handles token exchange and refresh. After OAuth authorization, the user's tokens are stored in Supabase. Subscriptions are created for measurement push notifications.
Create the measurement webhook and data Edge Functions
Create the measurement webhook and data Edge Functions
Create two Edge Functions: a webhook receiver that Withings calls when new measurements arrive, and a data fetcher for retrieving historical measurements. The Withings webhook sends a POST request with the user ID and measurement category when new data is available. The webhook payload is lightweight — it just tells you data is available, not what the data is. You then call the Measure API to fetch the actual measurements. This two-step pattern reduces webhook payload size and ensures you always get the latest data. For the measurement fetch, the Withings Measure API endpoint is POST to /measure with action=getmeas. Parameters include access_token, meastype (the measurement category number: 1=weight, 4=height, 5=fat free mass, 6=fat ratio, 8=fat mass weight, 9=diastolic BP, 10=systolic BP, 11=heart pulse, 17=SP02), startdate, enddate, and lastupdate (Unix timestamp). Withings returns measurements in a specific nested format: { status: 0, body: { measuregrps: [{ date, grpid, measures: [{ value, type, unit }] }] } }. Each measure value is an integer with a unit exponent — the actual value is value × 10^unit. For example, weight 7350 with unit -2 = 73.50 kg.
Create two Edge Functions: 1) supabase/functions/withings-webhook/index.ts — receive POST from Withings with userid and category, look up the app user_id from withings_user_mapping table, call the Withings Measure API with the user's access_token to fetch new measurements for that category, store results in body_measurements, bp_readings, or sleep_summaries Supabase tables depending on category, return 200. 2) supabase/functions/withings-data/index.ts — action='measurements' with user_id, meastype, startdate, enddate — fetch historical measurements from Withings Measure API; return structured array of {date, value, unit}.
Paste this in Lovable chat
Pro tip: Withings measurement values use an exponent format: actual_value = measure.value * Math.pow(10, measure.unit). For weight (type 1): value=7080, unit=-2 means 70.80 kg. Always apply this formula — displaying the raw integer value will show wildly incorrect numbers.
Expected result: The withings-webhook Edge Function receives Withings notifications and stores measurements in Supabase. When a user weighs themselves on their Withings scale, the measurement appears in the body_measurements table within seconds.
Common use cases
Weight and body composition trend tracker
A health management app receives automatic updates whenever a user weighs themselves on their Withings scale. A webhook Edge Function processes the notification, fetches the actual weight and body composition measurements, and stores them in Supabase. The dashboard shows weight trend, body fat trend, and muscle mass over the past 90 days with goal tracking.
Build a weight tracker using Withings scale data. Set up a webhook subscription to receive notifications when new body measurements arrive. When notified, call the Withings Measure API to fetch the new weight, BMI, fat mass, and muscle mass readings. Store in a Supabase body_measurements table with user_id, date, weight_kg, bmi, fat_mass_kg, muscle_mass_kg. Display a 90-day weight trend line chart with a goal weight line, body composition breakdown as a stacked area chart, and latest measurement stats.
Copy this prompt to try it in Lovable
Blood pressure monitoring dashboard
A remote patient monitoring app collects blood pressure readings from Withings BPM monitors. Each reading is received via webhook and stored with classification (normal, elevated, hypertensive). The dashboard shows a blood pressure trend chart with AHA classification color coding, average readings by time of day, and a flagging system for readings above hypertensive thresholds.
Build a blood pressure dashboard. Subscribe to Withings measurements for blood pressure type. When webhook fires, fetch the reading with systolic, diastolic, and heart rate. Classify each reading: normal (<120/80), elevated (120-129/<80), hypertension stage 1 (130-139/80-89), hypertension stage 2 (>=140/>=90). Store in bp_readings table with classification. Display: 30-day trend chart with color-coded classification bands, average morning vs. evening readings, count of readings in each category this month, and alerts for stage 2 hypertension readings.
Copy this prompt to try it in Lovable
Sleep quality analyzer with device data
A sleep improvement app uses Withings Sleep Analyzer data for accurate sleep stage analysis. Unlike Fitbit which estimates sleep from wrist movement, Withings' under-mattress sensor uses ballistocardiography for clinical-grade sleep stage detection. The app shows nightly sleep reports with REM cycles, breathing disturbances, and heart rate variability trends.
Build a sleep analyzer using Withings sleep data. Subscribe to sleep summary notifications. When webhook fires, fetch the sleep summary with duration, sleep score, deep sleep time, light sleep time, REM time, awake time, and apnea-hypopnea index. Store in sleep_summaries table. Display: nightly sleep stage breakdown as a timeline chart, 30-day sleep score trend, average sleep duration by day of week, HRV trend from sleep readings, and a weekly sleep quality report.
Copy this prompt to try it in Lovable
Troubleshooting
Withings returns status 401 instead of status 0 in token response
Cause: Withings uses a non-standard status code within the response body (not HTTP status). Status 401 in the body means authentication failed — typically wrong client_id, consumer_secret, or the authorization code was already used.
Solution: Verify WITHINGS_CLIENT_ID and WITHINGS_CONSUMER_SECRET in Cloud Secrets match exactly the values from developer.withings.com. Note that Withings uses 'consumer_secret' not 'client_secret'. Authorization codes are single-use — if you called the exchange endpoint twice with the same code, the second attempt fails. Confirm your callback handler calls the exchange endpoint only once.
1if (data.status !== 0) {2 throw new Error(`Withings API error ${data.status}: ${data.error ?? 'Unknown error'}`);3}Webhook endpoint receives notifications but measurement values appear wrong (e.g., weight shows as 7350 instead of 73.50)
Cause: Withings measurement values use an integer value with an exponent unit. The raw value must be multiplied by 10^unit to get the actual measurement. Displaying the raw integer without applying the exponent gives incorrect values.
Solution: Apply the Withings measurement value formula in your Edge Function: const actualValue = measure.value * Math.pow(10, measure.unit). This converts: value=7350, unit=-2 → 73.50 kg. Apply this to all measurement types — the exponent varies by measurement type and device.
1const actualValue = measure.value * Math.pow(10, measure.unit);2// Round to 2 decimal places for display3const displayValue = Math.round(actualValue * 100) / 100;Subscription creation fails or webhook never fires for new measurements
Cause: Withings subscription registration requires a valid access_token and the callback URL must be publicly accessible (not localhost). The subscription is per-user per-measurement-category — you need separate subscriptions for body, activity, and sleep data.
Solution: Verify the webhook URL in your subscription request is the deployed Edge Function URL (not the Lovable preview URL). Test your webhook URL is accessible from the internet using reqbin.com or similar. Check the Withings developer portal for active subscriptions and their status. Create subscriptions immediately after OAuth token exchange for all required measurement categories.
Best practices
- Create Withings subscriptions for all measurement categories immediately after the user completes OAuth authorization — without subscriptions, no measurement notifications will arrive.
- Apply the Withings measurement exponent formula (value × 10^unit) to every measurement value before storing or displaying — raw Withings integers are not human-readable values.
- Store Withings user IDs in a mapping table alongside Supabase user IDs — Withings webhooks only include the Withings user ID, not any reference to your app's users.
- Cache body measurement history in Supabase rather than fetching from Withings on each page load — the Withings Measure API is rate-limited and historical data doesn't change once recorded.
- For blood pressure readings, implement AHA classification logic server-side in your Edge Function rather than on the frontend — this ensures consistent classification regardless of which device or browser accesses the data.
- Implement measurement validation in your webhook receiver — if a measurement value is physiologically impossible (weight > 300 kg, blood pressure > 250/150), log and skip it rather than storing clearly erroneous data.
- Withings webhook notifications include multiple measurement groups from a single session — process all groups in the payload, not just the first, to capture all readings from one measurement session.
Alternatives
Fitbit API covers activity tracking and general wellness; choose Fitbit for step counting and daily activity rather than Withings' medical-grade body composition and blood pressure focus.
Garmin Connect provides detailed sports performance data for athletes; choose Garmin for training analytics rather than Withings' clinical health monitoring focus.
Google Fit aggregates data from Android health apps including Withings; use Google Fit when you need cross-device data aggregation rather than Withings-specific device accuracy.
Frequently asked questions
What makes Withings different from Fitbit or Apple Watch for health data?
Withings focuses on medical-grade accuracy in specific health metrics. Their blood pressure monitors are clinically validated and FDA-cleared. Their scales use multi-frequency bioelectrical impedance for more accurate body composition than wrist-based estimates. Their sleep analyzer uses a mattress sensor that captures breathing patterns and heart rate without being worn. For apps where health data accuracy matters clinically (remote patient monitoring, medical compliance), Withings' precision justifies its higher device price over consumer wearables.
Does Withings data work for HIPAA-compliant healthcare applications?
Withings itself has GDPR compliance and provides data through their Health Mate platform. For HIPAA compliance in a US healthcare context, you need to implement your own HIPAA-compliant data handling: Business Associate Agreement with Withings, encrypted data storage, access controls, audit logging, and breach notification procedures. Supabase has SOC 2 Type II but HIPAA BAA requires their Enterprise tier. Consult with a healthcare compliance expert before deploying health data applications in clinical settings.
Can I access data from Withings devices I don't own (e.g., in a clinic setting)?
Yes — the Withings API uses OAuth2, so clinic staff can authorize your app to access patient data from their Withings accounts. Each patient who consents authorizes your app independently through the standard OAuth2 flow. For clinical deployments with many patients, implement a patient onboarding flow that walks them through connecting their Withings account and clearly explains what data your app will access.
What types of Withings devices are supported by the API?
The Withings API supports data from all Withings connected devices: Body+ and Body Cardio smart scales (weight, BMI, body composition), BPM Core and BPM Connect blood pressure monitors, ScanWatch and Move ECG smartwatches (activity, sleep, ECG, SpO2), Sleep Analyzer mat (sleep stages, breathing, HRV), Thermo connected thermometer, and BPM Connect Pro medical-grade blood pressure monitor. All devices sync through the Withings Health Mate app and their data is accessible via the same API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation