How to Integrate Terra (Health-Data Aggregator) (2026)
Updated July 9, 2026
Terra is a health-data aggregator: instead of integrating Garmin, Fitbit, Oura, Whoop, Strava, Apple Health, and hundreds of other sources one at a time, you integrate Terra once and it returns all of them in a single normalized schema. Auth to Terra is a simple API-key model — every request carries a dev-id and an x-api-key header — while Terra brokers each provider's OAuth for you behind a hosted Connect widget. The one-line "how": your backend mints a widget session, the user picks and authorizes their device through Terra, and Terra POSTs normalized data to your webhook from then on.
That last point is the whole reason to use an aggregator, so hold onto it: one integration, many providers, delivered over one webhook. You never write provider-specific parsing. For a broader look at where Terra sits among aggregators, see health-data aggregator APIs and the head-to-head Terra vs Vital comparison.
One important caveat before you start
Terra shields you from writing each provider's OAuth flow — but not always from each provider's paperwork. For several popular providers (commonly Garmin, Whoop, Strava, and Oura), you must still register your own developer credentials with that provider and hand them to Terra (Dashboard, then Connections, then the provider, then Edit). So "one integration" is completely true for your code and your data schema; you may still complete N provider registrations before those providers light up. Budget for that onboarding time up front. Verify the exact list of providers that need your own credentials in the current Terra docs, since it changes.
What you'll need
- A Terra account (tryterra.co) with a project — this gives you a
dev-idand anx-api-key. - A Destination configured in the Terra dashboard: the HTTPS webhook URL where Terra will POST auth events and normalized data.
- A backend to call Terra's
/authendpoints. The API key must never be exposed client-side. - For providers that require it (Garmin, Whoop, Strava, Oura, and others), your own developer credentials with those providers, entered into the Terra dashboard.
- Base URL for all API calls:
https://api.tryterra.co/v2/. As of 2026, verify current endpoints and field names against docs.tryterra.co.
Step 1: Get your credentials and set up a Destination
Create a project in the Terra dashboard and copy your dev-id and x-api-key. Every API call to Terra sends both as headers:
dev-id: YOUR_DEV_ID
x-api-key: YOUR_API_KEY
Then configure a Destination — the webhook URL Terra will deliver events to (for example, https://yourapp.com/terra/webhook). This single endpoint is where every provider's data will arrive, normalized. Because everything funnels through here, you build and test one receiver, not one per wearable.
Step 2: Register your own credentials for the providers that need them
Skip this step for providers Terra fully brokers. For Garmin, Whoop, Strava, Oura and any others flagged in the docs, go to the Terra dashboard, then Connections, select the provider, choose Edit, and paste in the client ID/secret you obtained from that provider's own developer program. Until you do this, those providers will not appear as authorizable options in your widget. This is the "N registrations" part of the caveat above — it is dashboard configuration, not code.
Step 3: Generate a widget session from your backend
To connect a user, your backend calls Terra's Connect endpoint to mint a hosted widget session. You pass the list of providers to offer, plus your own reference_id (your internal user ID) so you can correlate Terra's user back to your account.
curl -X POST "https://api.tryterra.co/v2/auth/generateWidgetSession" \
-H "dev-id: YOUR_DEV_ID" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reference_id": "user-1234",
"providers": "GARMIN,FITBIT,OURA,WHOOP,STRAVA",
"language": "en",
"auth_success_redirect_url": "https://yourapp.com/success",
"auth_failure_redirect_url": "https://yourapp.com/failure"
}'
The response includes a session_id, a url, a status, and expires_in (the session is short-lived, roughly 900 seconds). Note how providers is just a comma-separated list — adding a new wearable to your product is a one-line change here, not a new integration.
Step 4: Send the user to the widget and handle the auth webhook
Open the returned url for the user (redirect, in-app browser, or webview). Terra hosts the whole flow: the user picks their provider and authorizes it, and Terra runs that provider's OAuth for you. On success the user is sent to your auth_success_redirect_url.
The authoritative signal, though, is the webhook. Terra fires an auth event to your Destination containing the Terra user_id and your reference_id. Store that user_id against your account — it is the key you'll use for every future data pull and the routing key on every incoming payload.
Step 5: Receive normalized data on your webhook
From then on, Terra pushes normalized data to your one webhook as events. Every payload carries a type (activity, sleep, daily, body, nutrition, menstruation, athlete, and auth) and a user block with the user_id, your reference_id, and which provider it came from. The data array is the same shape regardless of whether it originated from Garmin, Whoop, or Oura.
{
"type": "activity",
"user": {
"user_id": "9d5dc9eb-3e02-4b37-9a7b-b14b5a5a2b93",
"reference_id": "user-1234",
"provider": "WHOOP",
"active": true,
"last_webhook_update": "2026-07-09T11:13:43.360227+00:00"
},
"data": [ { "metadata": {} } ]
}
Route on type and user.user_id. Because the schema is identical across providers, you write one handler for activity, one for sleep, and so on — never a Garmin parser and a separate Whoop parser.
Step 6: Verify the webhook signature
Terra signs every webhook so you can confirm it genuinely came from Terra. The request carries a terra-signature header in the form t=<timestamp>,v1=<signature>. Terra signs with HMAC-SHA256: recompute the signature over the timestamp plus the raw request body using your signing secret, then compare it to the v1 value. Reject the request on mismatch, and read the raw body before any JSON parsing so the bytes match exactly.
const crypto = require("crypto");
function verifyTerraSignature(rawBody, header, secret) {
// header example: "t=1720523623,v1=abc123..."
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=", 2)) // split once: values never contain "="
);
if (!parts.t || !parts.v1) return false;
const signedPayload = `${parts.t}.${rawBody}`; // verify exact layout in current docs
const expected = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
const provided = Buffer.from(parts.v1);
const expectedBuf = Buffer.from(expected);
// Length check first: timingSafeEqual throws if the buffers differ in length,
// so a forged signature of the wrong length would otherwise crash the handler.
return (
provided.length === expectedBuf.length &&
crypto.timingSafeEqual(expectedBuf, provided)
);
}
Terra's official SDKs expose a helper (verify_terra_webhook_signature(...)) that does this for you — prefer it, and verify the exact signed-string construction in the current docs, since the precise layout can differ.
Step 7: Pull historical data via REST (optional)
Webhooks give you new data as it arrives, but on first connect you'll usually want backfill. Terra exposes REST endpoints per datatype — /v2/activity, /v2/sleep, /v2/daily, /v2/body, /v2/athlete, /v2/nutrition, /v2/menstruation — all keyed by the Terra user_id plus a date range.
curl -X GET "https://api.tryterra.co/v2/activity?user_id=USER_ID&start_date=2026-07-01&end_date=2026-07-08&to_webhook=false&with_samples=true" \
-H "dev-id: YOUR_DEV_ID" \
-H "x-api-key: YOUR_API_KEY"
Set to_webhook=true to have a large historical pull delivered asynchronously to your Destination instead of in the response, and with_samples=true for granular sample-level data. Same base URL, same headers, same normalized schema as the webhooks.
Gotchas and production notes
- Register provider credentials early. The most common launch surprise is Garmin/Whoop/Strava/Oura not appearing in the widget because you haven't yet supplied your own developer credentials in the dashboard. Start those provider registrations before you need them.
- Never expose the API key. Call
/authendpoints and any data pulls only from your backend. The widgeturlis the only thing that ever reaches the client. - Verify signatures on day one. Terra webhooks are HMAC-SHA256 signed; implement
terra-signatureverification from the start rather than retrofitting it. - Widget sessions are short-lived. Generate one per connection attempt (roughly 900 seconds); don't cache and reuse them.
- Pricing is usage/credit-based — model it. As of 2026, Terra is a subscription plus credit-based usage model; entry tier is reported around $399/mo billed annually (about $499 monthly) including roughly 100,000 credits/mo, with credits scaling on active authentications and events. All figures are volatile — confirm on tryterra.co/pricing before you budget.
- Aggregator, not magic. Terra removes per-provider OAuth code and per-provider parsing; it does not remove per-provider app approval where the provider requires it. That trade — a little dashboard paperwork for one codebase across many wearables — is exactly what you're buying.
Frequently asked questions
- What makes Terra different from integrating each wearable directly?
- Terra is an aggregator: you build one integration and receive Garmin, Fitbit, Oura, Whoop, Strava, Apple Health, and 500-plus other sources normalized into a single schema over one webhook. Instead of writing and maintaining separate OAuth flows and parsers per device, you write one receiver. The trade-off is usage-based pricing and, for some providers, still registering your own developer credentials.
- Do I still need my own developer credentials with each provider?
- For most providers Terra brokers the auth entirely. But for several popular ones (commonly Garmin, Whoop, Strava, and Oura) you must register your own developer credentials with that provider and enter them in the Terra dashboard under Connections. Terra shields you from writing the OAuth code, not always from the provider's app-approval process. Verify the current list in Terra's docs, since it changes.
- How does a user connect their wearable through Terra?
- Your backend calls POST /v2/auth/generateWidgetSession with a list of providers and your reference_id, and Terra returns a hosted widget url. You send the user to that url, where they pick their provider and authorize it while Terra runs that provider's OAuth. On success Terra fires an auth webhook with the Terra user_id you use for all subsequent data.
- How do I verify a Terra webhook is authentic?
- Terra signs each webhook with HMAC-SHA256 and sends a terra-signature header in the form t=timestamp,v1=signature. Recompute the signature over the timestamp plus the raw request body using your signing secret and compare it to v1, rejecting on mismatch. Terra's SDKs expose a verify_terra_webhook_signature helper; confirm the exact signed-string layout in the current docs.
- How much does Terra cost?
- As of 2026 Terra uses a subscription plus credit-based usage model. The entry tier is reported around $399/mo billed annually (about $499 monthly) including roughly 100,000 credits per month, with credits scaling on active authentications and events. All figures are volatile, so verify current numbers on tryterra.co/pricing before budgeting.
Keep reading
Independent comparison, last reviewed July 9, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All integration guides · by AIFitnessAPI