June 1, 2024 · 6 min read

Build a Health Agent with Claude and Sahha

Use Sahha webhooks and Anthropic's Claude to build an AI health agent that turns real-time wearable data into structured insights for your app users.

This guide shows you how to take a live Sahha webhook event, pass the relevant Sahha data into Claude, and return a structured output that your app can display or act on.

What you are building

The integration flow is:

Sahha SDK in your app

Sahha generates data

Sahha webhook sends event to your backend

Your backend extracts and formats the payload

Claude Sonnet generates an output

Your app stores or displays the result

A common use case is turning fresh Sahha data into:

  • a daily summary
  • a personalised engagement message
  • a short lifestyle insight
  • a structured JSON payload for your UI

Prerequisites

Before you start, make sure you already have:

  • Sahha SDK integrated into your app
  • a live Sahha webhook configured and receiving events
  • a backend endpoint that can receive webhook payloads
  • an Anthropic API key
Prerequisite: This guide assumes your app is already sending data through the Sahha SDK and that your Sahha webhook is live.

Keep your Claude integration on your server, not in your client app.

Your backend should:

  1. receive the Sahha webhook
  2. validate and parse the incoming payload
  3. extract the Sahha fields you want to use
  4. build a prompt for Claude
  5. request a structured output
  6. store or return the result

Why use Claude here?

Claude is useful when you want to transform Sahha data into consistent, product-ready outputs.

For this workflow, structured outputs are especially useful because they let you return machine-readable JSON that can be rendered directly in your UI or used in downstream automation.

Example output shape

In this example, Claude will return:

{
  "headline": "Poor recovery trend detected",
  "summary": "The user appears to have had reduced recovery over the last 3 days.",
  "recommendation": "Reduce cognitive load tonight and prioritise an earlier sleep window.",
  "tone": "supportive"
}

1. Install the Anthropic SDK

npm install @anthropic-ai/sdk

2. Create a webhook endpoint

Below is a minimal Node.js example using Express. You can adapt the same pattern for Next.js, Fastify, Cloudflare Workers, or your preferred backend framework.

import express from 'express'

const app = express()
app.use(express.json())

app.post('/webhooks/sahha', async (req, res) => {
  try {
    const payload = req.body

    // Optional: validate webhook authenticity here.
    // Optional: ignore event types you do not want to process.

    const result = await generateClaudeOutput(payload)

    // Save the result to your database, send it to your app,
    // or trigger the next step in your workflow.
    console.log(result)

    return res.status(200).json({ ok: true, result })
  } catch (error) {
    console.error(error)
    return res.status(500).json({
      ok: false,
      error: 'Failed to process webhook',
    })
  }
})

app.listen(3000, () => {
  console.log('Listening on port 3000')
})

3. Extract the Sahha data you want to send

Your webhook payload may contain more data than you need. In most cases, you should reduce it to only the fields that matter for the output you want to generate.

For example:

function mapSahhaPayload(payload: any) {
  return {
    profileId: payload.profileId,
    timestamp: payload.timestamp,
    scores: payload.scores,
    biomarkers: payload.biomarkers,
    // Add or remove fields based on your webhook payload shape.
  }
}

The goal is to give Claude enough context to generate a strong answer without sending unnecessary raw data.

4. Send the Sahha context to Claude

This example uses the Anthropic Messages API through the official SDK with a Sonnet model and JSON schema output.

import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
})

async function generateClaudeOutput(payload: any) {
  const sahha = mapSahhaPayload(payload)

  const response = await client.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 400,
    system:
      'You are a behavioural health assistant. Return concise, safe, supportive outputs for use inside a product experience.',
    messages: [
      {
        role: 'user',
        content: [
          'You will receive Sahha webhook data from a health and lifestyle application.',
          'Using only the data below, create a short output for the end user.',
          'Do not diagnose.',
          'Do not make medical claims.',
          'Keep the response practical, brief, and supportive.',
          '',
          'Sahha data:',
          JSON.stringify(sahha, null, 2),
        ].join('\n'),
      },
    ],
    output_config: {
      format: {
        type: 'json_schema',
        schema: {
          type: 'object',
          additionalProperties: false,
          properties: {
            headline: { type: 'string' },
            summary: { type: 'string' },
            recommendation: { type: 'string' },
            tone: {
              type: 'string',
              enum: ['supportive', 'neutral', 'encouraging'],
            },
          },
          required: ['headline', 'summary', 'recommendation', 'tone'],
        },
      },
    },
  })

  const textBlock = response.content.find((block) => block.type === 'text')

  if (!textBlock) {
    throw new Error('Claude did not return a text response')
  }

  return JSON.parse(textBlock.text)
}

5. Return or store the output

Once Claude responds, you can:

  • store the output in your database
  • attach it to a user timeline or feed
  • send it to your frontend in real time
  • use it to trigger another workflow

Example response:

{
  "headline": "Poor recovery trend detected",
  "summary": "Your recent data suggests recovery has trended lower over the last few days.",
  "recommendation": "Aim for a lighter evening and an earlier sleep window tonight.",
  "tone": "supportive"
}

Prompting tips

A few implementation details matter here.

Keep the Sahha context focused

Only send the fields that are relevant to the output you want. Cleaner inputs usually lead to more predictable outputs.

Keep the schema tight

If the response is going into a product UI, define a strict JSON schema and keep the number of fields small.

Be explicit about boundaries

If this output is user-facing, instruct the model clearly:

Avoid medical claims.
Do not diagnose.
Keep the tone supportive.
Write for an end user, not a clinician.

Plan for refusals and token limits

If Claude refuses the request or hits max_tokens, the output may not match your schema. Your backend should handle those cases gracefully.

Testing the flow

A simple way to test this integration is:

  1. trigger a real or test Sahha event
  2. confirm your webhook receives the payload
  3. log the mapped Sahha data
  4. send it to Claude
  5. inspect the returned JSON before saving it

You should also test:

  • missing fields
  • malformed payloads
  • duplicate webhook deliveries
  • Anthropic timeout or non-200 responses
  • incomplete outputs caused by token limits

Production notes

Before shipping this flow, make sure you:

  • verify webhook authenticity
  • implement retries and idempotency
  • avoid passing unnecessary personal data
  • log failures without exposing sensitive data
  • monitor cost and latency
  • validate the parsed output before saving it
Production recommendation: Do not pass unnecessary health or behavioural data to third-party services. Only send the minimum context required to generate the output you need.

Example use cases

You can reuse this pattern for:

  • daily readiness summaries
  • recovery-based engagement copy
  • behavioural nudges
  • personalised onboarding follow-ups
  • coach-facing or admin-facing summaries

Next step

Once this is working, the next improvement is usually to standardise multiple output types.

For example, you might create separate prompt templates and schemas for:

  • user-facing summaries
  • coach-facing insights
  • high-risk flags
  • weekly recaps

That lets the same Sahha webhook power multiple downstream experiences.