# How to Integrate Lovable with Withings

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

**Expected result:** Your Withings application is registered with Client ID and Consumer Secret saved. Callback URL and webhook URL are configured.

### 2. 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.

```
// supabase/functions/withings-oauth/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const CLIENT_ID = Deno.env.get("WITHINGS_CLIENT_ID") ?? "";
const CONSUMER_SECRET = Deno.env.get("WITHINGS_CONSUMER_SECRET") ?? "";
const REDIRECT_URI = Deno.env.get("WITHINGS_REDIRECT_URI") ?? "";
const API_BASE = Deno.env.get("WITHINGS_API_BASE") ?? "https://wbsapi.withings.net";
const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" };

serve(async (req) => {
  if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
  const supabase = createClient(Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "");
  const body = await req.json();

  try {
    if (body.action === "exchange") {
      const { code, user_id } = body;
      const res = await fetch(`${API_BASE}/v2/oauth2`, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({ action: "requesttoken", grant_type: "authorization_code", client_id: CLIENT_ID, consumer_secret: CONSUMER_SECRET, code, redirect_uri: REDIRECT_URI }),
      });
      const data = await res.json();
      if (data.status !== 0) throw new Error(`Withings error ${data.status}: ${data.error}`);
      const { access_token, refresh_token, expires_in, userid } = data.body;
      const expiresAt = new Date(Date.now() + expires_in * 1000).toISOString();
      await supabase.from("withings_tokens").upsert({ user_id, access_token, refresh_token, expires_at: expiresAt, withings_user_id: userid }, { onConflict: "user_id" });
      return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
    }

    if (body.action === "get-token") {
      const { user_id } = body;
      const { data: token } = await supabase.from("withings_tokens").select("*").eq("user_id", user_id).single();
      if (!token) throw new Error("No Withings connection found.");
      if (new Date(token.expires_at) < new Date(Date.now() + 900_000)) {
        const res = await fetch(`${API_BASE}/v2/oauth2`, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: new URLSearchParams({ action: "refreshtoken", client_id: CLIENT_ID, consumer_secret: CONSUMER_SECRET, refresh_token: token.refresh_token }),
        });
        const data = await res.json();
        if (data.status !== 0) throw new Error(`Token refresh failed: ${data.error}`);
        const expiresAt = new Date(Date.now() + data.body.expires_in * 1000).toISOString();
        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);
        return new Response(JSON.stringify({ access_token: data.body.access_token }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
      }
      return new Response(JSON.stringify({ access_token: token.access_token }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
    }

    return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: corsHeaders });
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });
  }
});
```

**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.

### 3. 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.

**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.

## 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.

## 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.

Prompt example:

```
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.
```

### 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.

Prompt example:

```
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.
```

### 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.

Prompt example:

```
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.
```

## 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.

```
if (data.status !== 0) {
  throw new Error(`Withings API error ${data.status}: ${data.error ?? 'Unknown error'}`);
}
```

### 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.

```
const actualValue = measure.value * Math.pow(10, measure.unit);
// Round to 2 decimal places for display
const 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.

## 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.

---

Source: https://www.rapidevelopers.com/lovable-integration/withings
© RapidDev — https://www.rapidevelopers.com/lovable-integration/withings
