Skip to content
AF
Integration Guides

How to Integrate Terra (Health-Data Aggregator) (2026)

Last verified October 17, 2026 · 10 min read

Sources checked (21) · October 17, 2026
Terra is a health-data aggregator: you integrate once and it returns normalized data from Garmin, Fitbit, Oura, WHOOP, Strava, Apple Health and, on its own count, 500+ providers behind one schema. Your backend authenticates with dev-id and x-api-key headers, creates a widget session at POST https://access.tryterra.co/api/widget/session so the user picks and authorizes their provider, and receives normalized events at a destination such as a webhook, verified with an HMAC-SHA256 terra-signature header. As of October 17, 2026, the older /v2/auth/generateWidgetSession endpoint is closed to accounts created after September 3, 2026 and planned for deprecation on November 3, 2026, and some providers, including WHOOP, Dexcom and Strava, still need your own developer app configured in Terra's dashboard.

Covered here:TerraWHOOPGarminStravaOuraFitbit

The whole reason to use an aggregator, and the thing to hold onto: one integration, many providers, delivered to one destination. You never write provider-specific parsing. For where Terra sits among aggregators, see health-data aggregator APIs and the head-to-head Terra vs Vital comparison; the Terra API overview collects how you get access and every page on this site that covers Terra. This guide is also one step on the reading path for supporting every wearable through one aggregator, which puts the pages for that job in working order.

Every endpoint, field and limit below is from Terra's docs, OpenAPI spec and pricing page as we read them on October 17, 2026.

What changed in this revision#

  • The widget endpoint. Terra relaunched its connect widget. New sessions are created at POST https://access.tryterra.co/api/widget/session. The older /v2/auth/generateWidgetSession, which an earlier version of this guide used, "keeps working today, with deprecation planned for 3 November 2026", and "Accounts created after 3 September 2026 cannot use it at all."
  • Which providers need your own credentials. An earlier version listed Garmin, Whoop, Strava and Oura. Terra's current docs name WHOOP, Dexcom, Huawei, Strava and Virtuagym; its integrations table also marks Garmin and Samsung as Required, and lists Oura as Managed. Step 2 has the detail.
  • Pricing is now published in full on tryterra.co/pricing; the numbers are under the gotchas below.

What you'll need#

  • A Terra account with a plan; the dashboard gives you a dev-id and an x-api-key. Terra keeps separate credentials for testing, staging and production, and the testing and staging environments are capped at 50 users each.
  • A destination configured in the Terra dashboard: a webhook URL, or a database, cloud storage bucket or queue that Terra writes into.
  • A backend to call Terra's API. Terra's docs are blunt: "Never share your x-api-key publicly or embed it directly in client-side code (e.g., mobile apps, frontend JavaScript)."
  • For WHOOP, Dexcom, Strava and the other providers in step 2, your own developer app with that provider.
  • The API surface: Terra's OpenAPI spec (version 2026.10.21.1) describes "The Terra API (v2 surface, served at access.tryterra.co/api/v2)".

Step 1: Get your credentials and set up a destination#

Create your account and copy the dev-id and x-api-key from the dashboard's API Keys page. Every API call sends both as headers:

dev-id: YOUR_DEV_ID
x-api-key: YOUR_API_KEY

Then choose where data goes. A webhook is the simplest destination; Terra can also deliver to "SQL databases (Postgres and MySQL), Supabase, cloud storage (S3, GCS and Azure Blob), queueing services (SQS and Kafka), MongoDB and Firestore". Because everything funnels through one destination, you build and test one receiver, not one per wearable.

Step 2: Register your own credentials for the providers that need them#

Terra's docs say it "manages API Keys of data sources, meaning you can connect users without having to apply for each API Key", but "Some providers require special processes before being able to start using them": WHOOP, Dexcom, Huawei, Strava and Virtuagym.

  • WHOOP and Dexcom. Each "requires every company to connect users through its own app" on its developer portal, with the redirect URL (and, for WHOOP, the webhook URL) "on a domain that belongs to you". In the dashboard, open Connections, choose the provider and follow the setup; it configures the custom domain and shows the URLs to paste into your app, and then you apply for production access with the provider.
  • Strava. Create an application at strava.com/settings/api, then enter its client ID and secret in the dashboard's Strava setup.
  • Garmin and Samsung. Terra's supported-integrations table marks both as Required for integration credentials, while its homepage FAQ says "Garmin data flows through Terra today" through Terra's long-standing partner integration. Check the Connections page for your account before promising Garmin support. For Samsung Health's direct route, Terra says to apply through Samsung's partnership portal and develop on the Health Connect route while you wait.

Plan as if a provider that needs special setup cannot be connected until that setup is finished. This is dashboard and paperwork time, not code, so start it first.

Step 3: Create a widget session from your backend#

To connect a user, your backend creates a hosted widget session and sends the user to the URL it returns. Pass your own reference_id (your internal user ID) so you can match Terra's user to yours.

curl --request POST "https://access.tryterra.co/api/widget/session" \
  --header "dev-id: YOUR_DEV_ID" \
  --header "x-api-key: YOUR_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "reference_id": "user-1234",
    "providers": "GARMIN,FITBIT,OURA,WHOOP,STRAVA",
    "auth_success_redirect_url": "https://yourapp.com/success",
    "auth_failure_redirect_url": "https://yourapp.com/failure"
  }'

Every body field is optional. providers is a comma-separated list "of providers to show, in the order given"; leave it out and the widget shows the sources enabled in your dashboard. A successful call returns HTTP 201 with the widget url, valid for expires_in seconds, which Terra documents as 24 hours. Create a fresh session per connect attempt rather than storing URLs. There is no language field any more: "The widget follows the end user's browser language automatically."

Migrating from the legacy endpoint? It lived at POST https://api.tryterra.co/v2/auth/generateWidgetSession, returned sessions valid for 900 seconds and took language, "providers": "multi-auth" and "providers": "ALL"; the new endpoint uses multi_auth: true and an omitted providers instead. Otherwise the response shape (session_id, url, status, expires_in) is unchanged.

Step 4: Send the user to the widget and handle the auth event#

Open the url in a new tab or an in-app browser. Terra's docs say: "Do not use WebView or iFrame for the authentication flow", because the user cannot see which domain they are typing a password into, and providers may block it. The user picks a provider and logs in with that provider's own login; afterwards they land on your success URL "with their Terra user_id and your reference_id as query parameters".

The authoritative signal is the auth event Terra sends to your destination, carrying the Terra user_id, your reference_id and the provider. Store that user_id against your account: it is the key for every later request and the routing key on every payload. If a user reconnects the same provider under the same reference_id, Terra "will delete the previous user record for that account and yield a new user_id", and "Terra sends a user_reauth event containing both the old and new user_id", so handle that event or new data will stop matching your records.

Step 5: Receive normalized data at your destination#

From then on, Terra pushes normalized data events as the provider syncs. Its event reference lists the data event types as activity, athlete, body, daily, hormone, menstruation, nutrition and sleep, alongside lifecycle events such as auth, deauth, user_reauth and access_revoked. Each payload names the user and provider; the data array has the same shape whichever wearable it came from. An abridged example:

{
  "type": "activity",
  "user": {
    "user_id": "4735eac4-31a2-47ef-8b11-51e98d84c680",
    "reference_id": "user-1234",
    "provider": "GARMIN"
  },
  "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. Terra's guidance for daily and body events is to overwrite the stored day with the latest payload, because those arrive several times a day as running totals.

Step 6: Verify the webhook signature#

"Every webhook sent by Terra includes an HMAC-based signature header", terra-signature, in the form t=<timestamp>,v1=<signature>. Terra signs with HMAC-SHA256: the signed payload is the timestamp, a ., then "The actual JSON payload (that is, the request body)", and the key is your endpoint's signing secret. Terra "requires the raw, unaltered body of the request to perform signature verification", so read the raw body before any JSON parsing. Ignore every scheme except v1, and compare in constant time.

const crypto = require("crypto");

function verifyTerraSignature(rawBody, header, secret) {
  // header example: "t=1723808700,v1=a5ee9dba..."
  let timestamp = null;
  const signatures = [];
  for (const part of header.split(",")) {
    const [key, value] = part.split("=", 2); // hex values never contain "="
    if (key === "t") timestamp = value;
    else if (key === "v1") signatures.push(value); // ignore other schemes
  }
  if (!timestamp || signatures.length === 0) return false;
  const expected = Buffer.from(
    crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex")
  );
  return signatures.some((sig) => {
    const provided = Buffer.from(sig);
    // timingSafeEqual throws on a length mismatch, so check length first
    return provided.length === expected.length && crypto.timingSafeEqual(provided, expected);
  });
}

Also check the timestamp against your own tolerance, as Terra's docs suggest, to reject replays. Terra recommends its official libraries over a manual check; its webhook docs show Python, JavaScript and Java SDK versions.

Respond quickly: "Terra waits 8 seconds for your server to respond before considering the request timed out." Failed deliveries are retried "up to 10 times with exponential backoff", over about eight hours, and then "the event is dropped. There is no notification when this happens", so return 200 at once, process asynchronously, and monitor your endpoint.

Step 7: Pull historical data via REST (optional)#

Webhooks give you new data as it arrives. For backfill on first connect, call the per-type endpoints, such as /activity (which "Returns completed workout sessions"), /sleep, /daily, /body, /nutrition, /menstruation and /athlete, keyed by the Terra user_id.

curl --get "https://access.tryterra.co/api/v2/activity" \
  --header "dev-id: YOUR_DEV_ID" \
  --header "x-api-key: YOUR_API_KEY" \
  --data-urlencode "user_id=USER_ID" \
  --data-urlencode "start_date=2026-10-01" \
  --data-urlencode "end_date=2026-10-08" \
  --data-urlencode "to_webhook=false" \
  --data-urlencode "with_samples=true"

Four details from Terra's historical-data guide catch people out:

  • user_id and start_date are required, and end_date "is exclusive on these endpoints", so pass the day after the last day you want.
  • to_webhook "will always be defaulted to true if not provided", which sends the data to your destination instead of the response; set it to false for a synchronous reply.
  • "For time ranges longer than 28 days, Terra will default to sending the data asynchronously", in chunks of at most 10MB.
  • with_samples must be true to receive granular data.

Gotchas and production notes#

  • Start provider registrations early. WHOOP and Dexcom need your own app on their portals and a domain you own, and production access is the provider's decision, not Terra's.
  • Never expose the API key. Create widget sessions and pull data only from your backend; calling the API from a browser also fails on CORS.
  • Verify signatures on day one and answer within 8 seconds, or events start retrying and, eventually, dropping.
  • Rate limits. Terra's overview says it "has no rate limits of its own", but its rate-limit reference says it "applies rate limits on data-read endpoints": no single request may span more than five years, and a user's requests may not total more than 6,000 requested days in an hour. Chunk backfills accordingly.
  • Pricing is credit-based, so model it. As of October 17, 2026, subscriptions "start from $399 per month billed annually, or $499 per month billed monthly", including 100,000 credits a month. Each connected data source costs 200 credits a month, the first 400 events per source are free, and extra credits start at $0.005. By our arithmetic, 100,000 credits cover 500 connected sources before events. Re-check 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, some dashboard paperwork for one codebase across many wearables, is what you are 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 other sources normalized into a single schema at one destination, from a webhook to a database or queue. Instead of separate OAuth flows and parsers per device, you write one receiver. The trade-offs are a credit-based bill, from $399 a month billed annually as of October 17, 2026, and your own developer app for some providers.
Do I still need my own developer credentials with each provider?
For some. As of October 17, 2026, Terra's docs say it manages provider API keys in general but names WHOOP, Dexcom, Huawei, Strava and Virtuagym as needing special processes; WHOOP and Dexcom require your own app on their developer portals with URLs on a domain you own. Its integrations table also marks Garmin and Samsung as Required, while its homepage says Garmin data flows through Terra's partner integration, so check your dashboard's Connections page.
How does a user connect their wearable through Terra?
Your backend calls POST https://access.tryterra.co/api/widget/session with your dev-id and x-api-key, optionally passing reference_id, a comma-separated providers list and redirect URLs. Terra answers with HTTP 201 and a widget url valid for 24 hours. The user opens it in a new tab or in-app browser, not a WebView or iframe, picks a provider and logs in, and Terra sends an auth event with the Terra user_id you store.
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. Concatenate the timestamp, a period and the raw request body, compute the HMAC with your endpoint's signing secret, and compare it in constant time with each v1 value, ignoring other schemes. Read the raw body before parsing JSON, or use Terra's official Python, JavaScript or Java libraries.
How much does Terra cost?
As of October 17, 2026, Terra's pricing page says subscriptions start from $399 a month billed annually or $499 billed monthly, including 100,000 credits a month. Each connected data source costs 200 credits a month, the first 400 events per source each month are free, extra events cost 0.5 credits, and extra credits start at $0.005, falling to $0.003 past 1,000,000. Add-ons such as streaming are priced separately.

All 113 questions in Integration Guides →

Keep reading

Elsewhere on the site

Pages that share this one’s concepts and sources, from other sections.

Next steps

Was this page useful?

Independent comparison, last reviewed October 17, 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