Withings OAuth

Mushu handles the Withings OAuth flow so your app can sync body composition data (weight, BMI, body fat, muscle mass) from Withings devices. Users authorize once; Mushu stores the tokens and handles refresh automatically.

How It Works

  1. Your app opens the Withings authorization URL (with your redirect_uri)
  2. User grants access in the Withings UI
  3. Withings redirects to Mushu's callback, which exchanges the code for tokens
  4. Mushu redirects to your redirect_uri — your app resumes
  5. Your app calls POST /withings/sync/{user_id} to pull measurements

Setup: Register Redirect URIs

Before initiating OAuth, register your app's allowed redirect URIs. Mushu validates the redirect_uri param against this allowlist — unregistered URIs are rejected.

Register via CLI:

mushu health redirect-uri add womoji://withings/success --app YOUR_APP_ID
mushu health redirect-uri list --app YOUR_APP_ID

Or via the dashboard: open your app → Health tab → Redirect URIs.

You can register multiple URIs (e.g. a custom scheme for iOS, an HTTPS URL for web). Any registered URI can be used at auth time.

Initiate OAuth

GET https://health.mushucorp.com/apps/{app_id}/withings/auth
  ?user_id=YOUR_USER_ID
  &redirect_uri=womoji://withings/success
ParameterRequiredDescription
user_idYesYour app's user identifier
redirect_uriYesWhere to send the user after OAuth completes. Must be pre-registered.

This endpoint returns a redirect to Withings. Open it in a browser or ASWebAuthenticationSession.

iOS: ASWebAuthenticationSession

Use ASWebAuthenticationSession with your custom scheme as callbackURLScheme. The session auto-dismisses when Mushu redirects to your URI after the OAuth callback.

import AuthenticationServices

func connectWithings(userId: String) {
    let authURL = URL(string: """
        https://health.mushucorp.com/apps/\(appId)/withings/auth
        ?user_id=\(userId)
        &redirect_uri=womoji://withings/success
        """.components(separatedBy: .whitespacesAndNewlines).joined())!

    let session = ASWebAuthenticationSession(
        url: authURL,
        callbackURLScheme: "womoji"  // must match your redirect_uri scheme
    ) { callbackURL, error in
        guard error == nil else { return }
        // OAuth complete — sync measurements
        syncWithings(userId: userId)
    }
    session.presentationContextProvider = self
    session.start()
}

Important: callbackURLScheme must match the scheme in your redirect_uri. If they don't match, ASWebAuthenticationSession will not intercept the callback and the sheet won't dismiss automatically.

Sync Measurements

After OAuth completes, pull the user's latest measurements:

POST https://health.mushucorp.com/apps/{app_id}/withings/sync/{user_id}
X-API-Key: YOUR_API_KEY

Mushu fetches from Withings and stores the data. Metrics are available immediately via the metrics API.

Check Connection Status

GET https://health.mushucorp.com/apps/{app_id}/withings/status/{user_id}
X-API-Key: YOUR_API_KEY

Returns { "connected": true } or { "connected": false }.

Disconnect

DELETE https://health.mushucorp.com/apps/{app_id}/withings/disconnect/{user_id}
X-API-Key: YOUR_API_KEY

Revokes Mushu's stored Withings tokens for this user. Does not revoke access on the Withings side.

Troubleshooting

ASWebAuthenticationSession sheet doesn't dismiss

The session only auto-dismisses when the callback URL matches callbackURLScheme. Make sure:

  • Your redirect_uri uses a custom scheme (e.g. womoji://, not https://)
  • callbackURLScheme matches that scheme exactly
  • The URI is registered in your app's Mushu health config

"redirect_uri not allowed" error

The URI wasn't registered. Run mushu health redirect-uri add YOUR_URI --app YOUR_APP_ID and try again.