> For the complete documentation index, see [llms.txt](https://docs.tryterra.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tryterra.co/introduction/examples/terra-basecamp.md).

# Terra Basecamp

Terra Basecamp is a complete health data platform built on Terra, and the first app in the [`terra-examples`](https://github.com/tryterra/terra-examples) repository. Users connect their wearables (Garmin, WHOOP, Oura, and more) and the app merges their data into a daily dashboard, with an AI assistant on top.

Scaffold your own copy with one command, deploy it, and use it as a starting point for your own app:

```bash
npm create tryterra-app
```

## What it demonstrates

* [User authentication](/unified-api/user-authentication/implementation-custom-ui.md) from a custom UI, with `reference_id` linking
* [Webhook ingestion](/unified-api/integration-setup/setting-up-data-destinations/webhooks.md) with signature verification and deduplication
* [Authentication event handling](/unified-api/user-authentication/handling-authentication-events.md): auth, deauth, reauth, and permission changes
* [Historical backfill](/unified-api/managing-user-health-data/requesting-historical-data.md): 30 days of history when a device connects
* Multi-device deduplication by provider priority
* Scheduled reconciliation every 6 hours via cron
* An AI health assistant built on Terra's MCP tools

<figure><img src="/files/ERaSMRricn6EIyn1UaIi" alt="The Terra Basecamp health dashboard showing daily sleep and stress scores, AI-generated insights, and an ask-about-your-health input"><figcaption><p>The dashboard summarises daily scores and insights across every connected device</p></figcaption></figure>

<figure><img src="/files/qXbW56scqVyiGel7d1NN" alt="The AI health assistant fetching health data, running analysis, and rendering an inline steps and calories chart"><figcaption><p>The AI assistant queries health data through Terra's MCP tools, runs analysis, and renders inline charts</p></figcaption></figure>

<figure><img src="/files/Li21jAJNq3rznIeB5gGb" alt="The Terra Basecamp Trends page charting sleep duration over a month with a 7 hour 32 minute average"><figcaption><p>The Trends page charts changes in your biomarkers over time</p></figcaption></figure>

## Tech stack

The whole app runs as a single Cloudflare Worker, with end-to-end type safety from Hono RPC.

| Layer        | Technology                                                         |
| ------------ | ------------------------------------------------------------------ |
| Frontend     | React 19, Vite 7, TanStack Router, TanStack Query, TanStack Store  |
| UI           | React Aria Components, Tailwind CSS v4, tailwind-variants          |
| Backend      | Hono on Cloudflare Workers                                         |
| Database     | Neon Postgres, Drizzle ORM                                         |
| Auth         | BetterAuth (email OTP)                                             |
| AI           | LLM assistant using Cloudflare Durable Objects (via Vercel AI SDK) |
| Provisioning | wrangler + neonctl (Cloudflare & Neon CLIs)                        |

## Run it yourself

Run the app end to end: deploy your own copy, connect a wearable, and see health data flow from the provider to the dashboard and the AI assistant.

{% stepper %}
{% step %}

#### Set up and deploy

Scaffold the app and run the setup wizard:

```bash
npm create tryterra-app
cd my-app
npm run setup
```

`npm run setup` signs you in to Cloudflare and Neon through your browser, prompts for your Terra credentials, provisions a Neon database, a Cloudflare Worker, and an R2 bucket, runs migrations, and deploys the app. When it finishes, it prints your **App URL**. Keep that handy for the next step. Re-running it is idempotent, so it only asks for what's missing.

{% hint style="info" %}
**Prerequisites:** [Node.js](https://nodejs.org/) v20+, a free [Neon](https://neon.tech/) account, and a free [Cloudflare](https://cloudflare.com/) account with [R2 enabled](https://dash.cloudflare.com/). Setup signs you in to both through your browser, so there are no keys to copy. It also asks for your Terra `dev-id`, API key, and webhook signing secret from the [dashboard](https://dashboard.tryterra.co/) (the signing secret lives on your webhook destination under **Connections**). The AI assistant is optional: it needs an [Anthropic](https://console.anthropic.com/) API key and the Cloudflare Workers Paid plan; without them the rest of the app runs on the free plan. [SendGrid](https://sendgrid.com/) is optional too (without it, OTP codes are logged to the console). The [repository README](https://github.com/tryterra/terra-examples/blob/main/packages/cli/templates/unified-api-web-app/README.md) covers every environment variable.
{% endhint %}
{% endstep %}

{% step %}

#### Point Terra webhooks at your app

In the Terra dashboard, set your [webhook destination](/unified-api/integration-setup/setting-up-data-destinations/webhooks.md) URL to your deployed Worker:

```
https://<your-app-url>/api/terra/webhook
```

Terra now delivers authentication events and health data straight to your copy of the app.
{% endstep %}

{% step %}

#### Sign in

Open your App URL in a browser and sign in with your email address.

{% hint style="info" %}
If you didn't configure SendGrid, the one-time code isn't emailed. Stream the Worker logs (for example with `npx wrangler tail` from your project) and read the code there.
{% endhint %}
{% endstep %}

{% step %}

#### Connect a wearable

Open the **Connectors** page, pick your provider (Garmin, WHOOP, Oura, Fitbit, and more), and complete the provider's sign-in flow. When you're redirected back, Terra sends an `auth` webhook, the app links the connection to your account, and a 30-day historical backfill starts automatically.

<figure><img src="/files/w7Mn5b6FSDumZzuPyfQj" alt="The Terra Basecamp Connectors page showing a connected Fitbit account and a searchable list of providers to connect"><figcaption><p>Pick a provider on the Connectors page and complete its sign-in flow</p></figcaption></figure>
{% endstep %}

{% step %}

#### Watch your data arrive

Open the **Dashboard**. As Terra delivers the backfill and new updates, your steps, heart rate, HRV, sleep, and stress fill in. Delivery timing varies by provider. If you connect a second device, overlapping data is merged by provider priority.

<figure><img src="/files/vxFbgjky7N9iYgevnAwq" alt="A connector detail page showing the Fitbit connection status, enabled scopes, and recent sync events for daily, activity, and body data"><figcaption><p>Each connector's detail page lists enabled scopes and recent sync events as Terra delivers your data</p></figcaption></figure>
{% endstep %}

{% step %}

#### Ask the AI assistant

Open **Chat** and ask about your data. Try "How did I sleep this week?" or "Chart my steps for the last 30 days". The assistant queries your data through Terra's MCP tools and renders inline charts.

{% hint style="info" %}
Chat needs an `ANTHROPIC_API_KEY` (added during setup) and the Cloudflare Workers Paid plan. Without it, the rest of the app still works.
{% endhint %}
{% endstep %}
{% endstepper %}

Develop locally with `npm run dev` (it runs against a separate dev database branch, so you can't break your deployed data), and ship changes with `npm run deploy`.

## How the Terra integration works

Two pieces are worth calling out. First, the webhook endpoint verifies Terra's signature against the raw request body **before** parsing JSON, then returns `200` immediately and processes the event asynchronously to stay inside Terra's webhook timeout.

```typescript
import { verifyTerraWebhookSignature } from "terra-api";

const terraWebhook = new Hono<{ Bindings: Env }>().post("/", async (c) => {
  // Raw body required: verify the signature before JSON parsing
  const rawBody = await c.req.text();
  const signature = c.req.header("terra-signature");

  try {
    await verifyTerraWebhookSignature(rawBody, signature, c.env.TERRA_WEBHOOK_SECRET);
  } catch {
    return c.json({ error: "Invalid signature" }, 401);
  }

  // Return 200 immediately; process async to stay within Terra's timeout
  c.executionCtx.waitUntil(processWebhookEvent(c.env, rawBody));
  return c.json({ success: true });
});
```

Second, the AI assistant connects to the [Terra MCP server](/unified-api/managing-user-health-data/receiving-data-updates.md) that Terra exposes per connection, then hands those tools straight to the model alongside its own chart-rendering tool, so the LLM can fetch a user's data and visualise it in one turn.

```typescript
// Terra exposes an MCP server per connection
const url = `https://access.tryterra.co/api/v2/mcp/${terraUserId}`;
await this.addMcpServer("terra", url, {
  transport: { headers: { "dev-id": env.TERRA_DEV_ID, "x-api-key": env.TERRA_API_KEY } },
});

const result = streamText({
  model: anthropic(CHAT_MODEL),
  messages: modelMessages,
  tools: { render_chart: renderChartTool, ...this.mcp.getAITools() },
});
```

## Explore the code

The [full source is on GitHub](https://github.com/tryterra/terra-examples/tree/main/packages/cli/templates/unified-api-web-app), including tests alongside the webhook, sync, and backfill modules. The repository also ships its own guides:

| Guide                                                                                                                                                          | What it covers                                                |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| [Terra webhooks](https://github.com/tryterra/terra-examples/blob/main/packages/cli/templates/unified-api-web-app/docs/terra-webhooks.md)                       | Webhook ingestion pipeline, archiving, and idempotency        |
| [Auth & reconciliation](https://github.com/tryterra/terra-examples/blob/main/packages/cli/templates/unified-api-web-app/docs/terra-auth-and-reconciliation.md) | Connection lifecycle and the 6-hourly reconciliation cron     |
| [Multi-device data](https://github.com/tryterra/terra-examples/blob/main/packages/cli/templates/unified-api-web-app/docs/terra-multi-device.md)                | Provider priority and deduplication strategy                  |
| [AI health assistant](https://github.com/tryterra/terra-examples/blob/main/packages/cli/templates/unified-api-web-app/docs/health-assistant.md)                | Chat agent architecture, Terra MCP tools, and inline charting |
| [Infrastructure](https://github.com/tryterra/terra-examples/blob/main/packages/cli/templates/unified-api-web-app/docs/infrastructure.md)                       | Provisioning and the one-command deployment pipeline          |
