Integration Best Practices
Building a robust health data integration requires more than API calls. This guide covers permission flows, error handling, data sync strategies, privacy compliance, and production deployment best practices.
1. Permission & Onboarding Flow
⚠️ The #1 Mistake: Asking for Everything Upfront
Many apps request all health permissions immediately during onboarding. This results in:
- 78% permission denial rate (users overwhelmed by scope)
- -42% conversion from signup to active user
- High abandonment before seeing any app value
Better approach: Progressive permissions - request only what's needed, when it's needed.
✅ Good Permission Flow (Progressive)
Step 1: Core Feature First (No Permissions)
Let users explore core features before asking for health data:
- Show demo/sample data
- Explain what the app does
- Build trust and value perception
Example: Fitness app shows workout library, nutrition tips before requesting step data
Step 2: Just-In-Time Permissions
Request permissions when user triggers relevant feature:
- User taps "Track My Steps" → request step count permission
- User views "Sleep Analysis" → request sleep permission
- User enables "Heart Rate Zones" → request HR permission
Result: +68% permission grant rate
Step 3: Clear Value Communication
Explain WHY you need each permission before requesting:
// Before showing system permission dialog
showCustomDialog({
title: "Enable Sleep Tracking",
message: "We'll analyze your sleep patterns to recommend optimal bedtimes and detect sleep debt.",
benefits: ["Personalized sleep schedule", "Sleep quality trends", "Energy predictions"],
onAccept: () => requestHealthKitPermission('sleepAnalysis')
})
Step 4: Graceful Degradation
App remains useful even if permissions denied:
- Denied sleep access: Offer manual sleep logging
- Denied step count: Provide workout timer instead
- Denied all: Focus on education/content features
Key: Never lock core features behind permissions
❌ Bad Permission Flow (All-or-Nothing)
Anti-Pattern: Immediate Permission Wall
// DON'T DO THIS
onAppLaunch() {
requestHealthKitPermission([
'stepCount', 'heartRate', 'sleepAnalysis',
'activeEnergy', 'weight', 'bodyFat',
'bloodPressure', 'vo2Max', 'respiratoryRate'
])
// 10+ permissions at once = user panic
}
Result: 78% denial rate
Anti-Pattern: No Explanation
// System dialog with no context
"MyApp would like to access your Health data"
[Don't Allow] [OK]
// User thinks: "Why? What data? No thanks."
Result: -42% conversion
2. Data Sync Strategy
Real-time vs Batch Sync
⚡ Real-time Sync (Webhooks)
When to use:
- Health alerts (anomaly detection)
- Churn prediction (engagement drops)
- Live coaching (workout adjustments)
- Mental health monitoring
Platforms supporting webhooks:
- Sahha - Score updates, biomarker changes
- Terra - Device data pushes
- Spike - Medical device events
- Rook - Batch only (no webhooks)
📊 Batch Sync (Polling)
When to use:
- Analytics dashboards
- Weekly/monthly reports
- Historical trend analysis
- Research data collection
Best practices:
- Poll every 1-6 hours (not more frequent)
- Use background fetch (iOS) / WorkManager (Android)
- Respect rate limits (150 req/hour typical)
- Implement exponential backoff on errors
Webhook Implementation Best Practices
// ✅ GOOD: Webhook endpoint with signature verification
app.post('/webhooks/sahha', async (req, res) => {
// 1. Verify signature (prevent spoofing)
const signature = req.headers['x-signature']
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex')
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' })
}
// 2. Acknowledge immediately (respond within 5 seconds)
res.status(200).json({ received: true })
// 3. Process asynchronously (don't block response)
processWebhookAsync(req.body)
.catch(err => logger.error('Webhook processing failed', err))
})
async function processWebhookAsync(payload) {
// 4. Idempotency check (prevent duplicate processing)
const eventId = payload.id
const processed = await db.query('SELECT 1 FROM processed_events WHERE id = ?', [eventId])
if (processed.length > 0) return
// 5. Process event
await handleSleepScoreUpdate(payload)
// 6. Mark as processed
await db.query('INSERT INTO processed_events (id, processed_at) VALUES (?, NOW())', [eventId])
}
Background Sync (Mobile Apps)
// ✅ iOS Background Fetch (every 1-6 hours)
func application(_ application: UIApplication,
performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
syncHealthData { result in
switch result {
case .newData:
completionHandler(.newData)
case .noData:
completionHandler(.noData)
case .failed:
completionHandler(.failed)
}
}
}
// ✅ Android WorkManager (periodic sync)
val syncWork = PeriodicWorkRequestBuilder(
repeatInterval = 6, repeatIntervalTimeUnit = TimeUnit.HOURS
).setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
).build()
WorkManager.getInstance(context).enqueue(syncWork)
3. Error Handling & Retry Logic
Common Failure Modes
- Rate limits: API throttling (150 req/hour typical)
- Token expiry: OAuth tokens expire (8 hours - 1 year depending on device)
- Network failures: User offline, API downtime
- Permissions revoked: User disables health data access
- Device disconnected: Wearable not syncing to platform
✅ Robust Error Handling
// ✅ GOOD: Exponential backoff with max retries
async function syncWithRetry(userId, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await fetchHealthData(userId)
return data
} catch (error) {
if (error.status === 429) {
// Rate limit - wait longer
const waitTime = Math.pow(2, attempt) * 1000 // 2s, 4s, 8s
await sleep(waitTime)
continue
} else if (error.status === 401) {
// Token expired - refresh and retry
await refreshToken(userId)
continue
} else if (error.status === 403) {
// Permissions revoked - notify user, stop retrying
await notifyPermissionsRevoked(userId)
throw error
} else if (error.status >= 500) {
// Server error - retry with backoff
if (attempt < maxRetries) {
await sleep(Math.pow(2, attempt) * 1000)
continue
}
}
// Unrecoverable error or max retries reached
throw error
}
}
}
❌ Bad Error Handling
// ❌ DON'T DO THIS: No retry, no context
async function syncHealthData(userId) {
try {
const data = await fetchHealthData(userId)
return data
} catch (error) {
console.log('Failed to sync') // Vague error, no recovery
return null // Silent failure - user never knows
}
}
4. Privacy & Compliance
HIPAA Compliance Checklist (if applicable)
GDPR Compliance Checklist (EU users)
Data Minimization
✅ Collect Only What You Need
// Fitness app only needs activity data
const dataTypes = [
'stepCount',
'activeEnergy',
'distanceWalking'
]
// Don't request: heart rate, sleep, weight, etc.
Benefits:
- Lower permission denial rate
- Reduced storage costs
- Simpler compliance (less sensitive data)
❌ Collect Everything "Just in Case"
// DON'T DO THIS
const dataTypes = [
'stepCount', 'heartRate', 'sleepAnalysis',
'weight', 'bodyFat', 'bloodPressure',
'bloodGlucose', 'vo2Max', 'respiratoryRate',
'menstrualFlow', 'sexualActivity'
// ... 50+ more biomarkers you don't use
]
Problems:
- High permission denial
- HIPAA/GDPR violations
- Unnecessary storage costs
5. Performance Optimization
Reduce API Calls
✅ Batch Queries
// GOOD: Single query for multiple biomarkers
const data = await sahha.getBiomarkers({
types: ['sleep', 'activity', 'readiness'],
startDate: '2025-10-01',
endDate: '2025-10-16'
})
// 1 API call for 3 biomarker types
❌ Individual Queries
// BAD: Separate query per biomarker
const sleep = await sahha.getSleep(...)
const activity = await sahha.getActivity(...)
const readiness = await sahha.getReadiness(...)
// 3 API calls (3x latency, 3x rate limit usage)
Cache Intelligently
// ✅ GOOD: Cache with TTL (Time To Live)
async function getHealthData(userId, date) {
const cacheKey = `health:${userId}:${date}`
// Check cache first
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
// Fetch from API
const data = await sahha.getBiomarkers({ userId, date })
// Cache with appropriate TTL
const ttl = isToday(date) ? 3600 : 86400 // 1 hour for today, 24 hours for past
await redis.setex(cacheKey, ttl, JSON.stringify(data))
return data
}
Pagination for Large Datasets
// ✅ GOOD: Paginate when fetching historical data
async function fetchAllBiomarkers(userId, startDate, endDate) {
let allData = []
let page = 1
const pageSize = 100
while (true) {
const response = await sahha.getBiomarkers({
userId,
startDate,
endDate,
page,
pageSize
})
allData = allData.concat(response.data)
if (response.data.length < pageSize) break // Last page
page++
}
return allData
}
6. User Experience Best Practices
Handle Missing Data Gracefully
✅ Explain Why Data is Missing
- No wearable connected: "Connect a wearable to see heart rate data, or use smartphone tracking for basic metrics"
- Device not synced: "Your Apple Watch hasn't synced in 3 days. Open the Health app to sync."
- Permissions denied: "Enable sleep tracking in Settings → Privacy → Health to see sleep analysis"
Always provide actionable next step
❌ Show Empty State Without Context
- "No data available" (Why? What can I do?)
- "Sync failed" (How do I fix it?)
- Blank screen (Is the app broken?)
Users assume app is broken, churn increases
Loading States
// ✅ GOOD: Progressive loading with skeleton UI
function HealthDashboard() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchHealthData().then(data => {
setData(data)
setLoading(false)
})
}, [])
if (loading) {
return // Show layout with animated placeholders
}
return
}
7. Testing Strategy
Pre-Production Testing Checklist
Staging Environment Best Practices
// ✅ GOOD: Separate staging credentials
const config = {
production: {
apiKey: process.env.SAHHA_PROD_API_KEY,
webhookUrl: 'https://api.myapp.com/webhooks/sahha'
},
staging: {
apiKey: process.env.SAHHA_STAGING_API_KEY,
webhookUrl: 'https://staging.myapp.com/webhooks/sahha'
}
}
// Use test/demo profiles in staging
const userId = IS_PRODUCTION ? user.id : 'demo-user-001'
8. Monitoring & Alerts
Key Metrics to Track
| Metric | Target | Alert Threshold |
|---|---|---|
| Permission grant rate | ≥60% | Drops below 50% |
| Sync success rate | ≥95% | Drops below 90% |
| API response time (p95) | ≤500ms | Exceeds 1000ms |
| Webhook delivery rate | ≥99% | Drops below 95% |
| Data freshness (median) | ≤6 hours | Exceeds 24 hours |
| Error rate | ≤1% | Exceeds 5% |
Logging Best Practices
// ✅ GOOD: Structured logging with context
logger.info('Health data sync started', {
userId: user.id,
dataTypes: ['sleep', 'activity'],
dateRange: { start: '2025-10-01', end: '2025-10-16' },
platform: 'sahha'
})
logger.error('Sync failed', {
userId: user.id,
error: err.message,
errorCode: err.status,
retryCount: 3,
platform: 'sahha'
})
// Enables filtering: "Show all Sahha errors for userId=123"
9. Production Deployment Checklist
Pre-Launch Checklist
10. Common Pitfalls to Avoid
❌ Don't Do This:
- Request all permissions upfront → Use progressive permissions
- Poll APIs every minute → Use webhooks or 1-6 hour intervals
- Ignore rate limits → Implement exponential backoff
- Store sensitive data unencrypted → Use AES-256 at rest, TLS 1.2+ in transit
- Assume data always exists → Handle missing data gracefully
- Hard-code API keys → Use environment variables
- Skip error logging → Implement structured logging
- Trust webhook payloads → Verify signatures
- Process webhooks synchronously → Use async background jobs
- Ignore HIPAA/GDPR → Consult legal counsel, implement compliance
✅ Do This Instead:
- Progressive permissions (just-in-time, with clear value communication)
- Webhook-first (real-time updates without polling)
- Retry with backoff (handle rate limits gracefully)
- Encrypt everything (at rest and in transit)
- Graceful degradation (app works even without health data)
- Environment-based config (separate staging/production)
- Structured logging (searchable, filterable logs)
- Signature verification (prevent webhook spoofing)
- Async processing (respond to webhooks within 5 seconds)
- Compliance by design (HIPAA/GDPR from day one)
Next Steps
- 🎯 Choosing a Health Data API - Platform selection guide
- 🔧 Platform API vs Direct Integration - Technical comparison
- 📱 Smartphone vs Wearable Data - Coverage vs precision
- 📖 Integration Approaches - Complete decision framework