Install
openclaw skills install slug-testStream and sync iPhone/Apple Watch HealthKit data via a local webhook server for AI analysis, recovery scoring, and health anomaly alerts.
openclaw skills install slug-testConnect your iPhone/Apple Watch health data to OpenClaw for AI-powered analysis.
HealthClaw streams Apple Health data (heart rate, HRV, sleep, steps, workouts) to your OpenClaw agent via a local webhook server. Once connected, your agent can calculate recovery scores, detect health anomalies, answer questions about your health trends, and proactively alert you when something looks off.
iPhone / Apple Watch
↓ (HealthKit → background sync)
iOS App (HealthClaw)
↓ (HTTPS POST)
Webhook Server ← npx healthclaw-webhook-server
↓
health-data.jsonl (append-only log)
↓
OpenClaw Agent (queries, crons, alerts)
| Concept | Description |
|---|---|
| Pairing | One-time setup: server issues a time-limited token (2 min), iOS app scans or opens the deep-link, exchanges it for a permanent API token stored securely on the server |
| Data sync | iOS app POSTs individual records to /api/health-sync or bulk batches to /api/health-sync/batch. Each record contains a type, value, unit, startDate, endDate, and optional metadata |
| Deduplication | Every record gets a deterministic ID from (type, startDate, endDate, value). The server keeps a SQLite dedupe index — re-syncing the same data is always safe, duplicates are silently dropped |
| Storage | All data is appended to health-data.jsonl in a platform-appropriate user directory (~/Library/Application Support/healthclaw-webhook on macOS) |
npx healthclaw-webhook-server
The server starts on port 3000 by default. Keep it running (consider a LaunchAgent / systemd service for persistence).
Optional environment variables:
PORT=3000 # Server port
HEALTHCLAW_DATA_DIR=~/custom/path # Override data directory
ADMIN_TOKEN=your-secret # Protect admin endpoints
The iOS app needs to reach your server from outside your local network.
Tailscale Funnel gives your machine a stable public HTTPS URL tied to your Tailscale domain — no dynamic DNS, no port forwarding needed.
# 1. Install Tailscale and log in (skip if already done)
# https://tailscale.com/download
tailscale login
# 2. Enable Funnel for port 3000
tailscale funnel --bg 3000
Your public URL will be:
https://<machine-name>.<tailnet-name>.ts.net
To find it:
tailscale funnel status
# or
tailscale status --json | grep DNSName
Note: Funnel runs in the background (
--bg). It persists across reboots. To stop it:tailscale funnel --bg --off
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
cloudflared tunnel --url http://localhost:3000
# Cloudflare prints a randomly-assigned https://xxxxx.trycloudflare.com URL
URLs are ephemeral — you'll need to re-pair the iOS app each time you restart the tunnel.
ngrok http 3000
# ngrok prints a https://xxxxx.ngrok-free.app URL
Same caveat as Cloudflare — URL changes on restart unless you have a paid plan.
Note your public URL — you'll need it in the next step when generating the pairing link.
curl -X POST https://your-public-url/admin/generate-pairing
Response:
{
"pairingToken": "abc123...",
"deepLink": "healthclaw://pair?url=...&token=...",
"openUrl": "https://your-public-url/pair/open?token=...",
"expiresInSeconds": 120
}
Open openUrl on your iPhone, or paste deepLink into Safari. The page has a button
that opens the HealthClaw app directly.
Token expires in 2 minutes. If it expires, run the curl command again.
The HealthClaw iOS companion app handles background HealthKit syncing.
Current status: App is pending App Store review.
TestFlight (beta): https://testflight.apple.com/join/SXDjT6vC
Once installed, open the app and follow the pairing flow. After pairing, the app will sync data automatically in the background — no need to keep it open.
curl https://your-public-url/health
# → { "status": "ok", "paired": true, ... }
Check that data is flowing:
tail -f ~/Library/Application\ Support/healthclaw-webhook/health-data.jsonl
Each line in health-data.jsonl is a JSON record:
{
"type": "HeartRate",
"value": 62,
"unit": "count/min",
"startDate": "2025-01-15T07:32:00Z",
"endDate": "2025-01-15T07:32:00Z",
"metadata": { "context": "resting" }
}
Common types: HeartRate, RestingHeartRate, HeartRateVariabilitySDNN,
StepCount, SleepAnalysis, ActiveEnergyBurned, DistanceWalkingRunning, and more.
| Example | Description |
|---|---|
| Recovery Score | Daily HRV + RHR + sleep score with cron job setup |
| Health Alerts | Proactive anomaly detection with configurable thresholds |
By default, the server runs in single-user (legacy) mode. To support multiple users (e.g. family members, clients), create users via the Admin API. Each user gets an isolated data directory — no data mixing between users.
curl -X POST https://your-public-url/admin/users \
-H "Content-Type: application/json" \
-H "x-admin-token: your-secret" \
-d '{"name": "alice"}'
Response (token is shown once only — save it):
{
"userId": "usr_a1b2c3d4",
"token": "64-char-hex-token...",
"name": "alice",
"createdAt": "2026-03-14T12:00:00.000Z"
}
curl https://your-public-url/admin/users \
-H "x-admin-token: your-secret"
Returns [{ userId, name, createdAt }] — tokens are never included in list responses.
The returned token is the API key the iOS app uses for syncing. Set it as the
x-api-token header when pairing or syncing. Each user's data is automatically
routed to their own directory:
{appDataRoot}/users/{userId}/health-data.jsonl
{appDataRoot}/users/{userId}/dedupe.db
The existing single-device permanent token continues to work. Its data stays at the
original path ({appDataRoot}/health-data.jsonl). No migration is needed — both
modes work side by side.
When querying health data for a specific user, read from their per-user path:
# Single (legacy) user — default path
cat ~/Library/Application\ Support/healthclaw-webhook/health-data.jsonl
# Multi-user — per-user path
cat ~/Library/Application\ Support/healthclaw-webhook/users/usr_a1b2c3d4/health-data.jsonl
Full API spec: webhook-server/docs/API_SPEC.md
Key endpoints:
GET /health — server status + pairing statePOST /admin/generate-pairing — create a new pairing linkPOST /api/pair?token=<token> — complete device pairing (called by iOS app)POST /api/health-sync — ingest a single recordPOST /api/health-sync/batch — ingest up to 5000 records in one requestPOST /admin/users — create a new user (returns token once)GET /admin/users — list all users (no tokens)GET /admin/device-info — check paired device metadata