> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waterr.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Examples

> Complete integration examples for common use cases.

## Overview

These examples show complete integrations from API setup to result retrieval. Copy and adapt them for your use case.

## Example 1: Embed Interview Practice on Your Website

Build a page where candidates practice interviews and you get scored results.
The AI interviewer pulls each candidate's real CV from your ATS mid-session
via a [custom tool](/api-reference/custom-functions) so questions reference
their actual experience.

### Setup

```javascript theme={null}
// server.js (Node.js/Express)
const express = require('express');
const app = express();
const API = 'https://api.waterr.ai/v1';
const API_KEY = process.env.WATERR_API_KEY;  // wai_...

const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type': 'application/json',
};

// Create a meeting when a candidate clicks "Start Practice"
app.post('/api/start-session', async (req, res) => {
  const { candidateName, scenarioId } = req.body;

  const meeting = await fetch(`${API}/meetings`, {
    method: 'POST', headers,
    body: JSON.stringify({
      person_name: candidateName,
      scenario_id: scenarioId,
      meeting_type: 'regular',
    }),
  }).then(r => r.json());

  res.json({
    meetingId: meeting.data.id,
    roomUrl: meeting.data.daily_meeting_url,
    token: meeting.data.room_token,
  });
});

// Fetch results after the session
app.get('/api/results/:meetingId', async (req, res) => {
  const analysis = await fetch(
    `${API}/analyses/meeting/${req.params.meetingId}`,
    { headers }
  ).then(r => r.json());

  res.json(analysis);
});
```

### Register a custom tool (one-time)

Register a `lookup_candidate` tool once on your account so the AI interviewer
can pull the candidate's CV from your ATS during the session. The function
runs **on your server** — Waterr just calls your webhook when the LLM emits
the tool call.

```bash theme={null}
# 1) Define the tool on your account
curl -X POST https://api.waterr.ai/v1/custom-functions \
  -H "Authorization: Bearer wai_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "lookup_candidate",
    "description": "Fetch the candidate'\''s CV from the ATS. Call once at the start of the interview so you can reference their real experience.",
    "parameters_schema": {
      "type": "object",
      "properties": {
        "candidate_email": {
          "type": "string",
          "description": "Candidate'\''s email — used as the ATS key."
        }
      },
      "required": ["candidate_email"]
    },
    "execution_mode": "webhook",
    "webhook_url": "https://your-app.com/waterr/tools/lookup_candidate",
    "webhook_secret": "whsec_replace_with_random_64_chars",
    "timeout_ms": 8000
  }'
# → { "data": { "id": "fn-lookup-candidate", ... } }

# 2) Attach it to the interview scenario
curl -X POST https://api.waterr.ai/v1/scenarios/{scenarioId}/custom-functions/fn-lookup-candidate/attach \
  -H "Authorization: Bearer wai_<your_key>"
```

### Handle the tool call (webhook)

```javascript theme={null}
// Same server.js — Waterr POSTs here when the AI calls `lookup_candidate`
const crypto = require('crypto');

app.post(
  '/waterr/tools/lookup_candidate',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    // Verify the HMAC signature using the raw body
    const expected =
      'sha256=' +
      crypto
        .createHmac('sha256', process.env.WATERR_TOOL_SECRET)
        .update(req.body)
        .digest('hex');
    const signature = req.headers['x-waterr-signature'];
    if (
      !signature ||
      !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
    ) {
      return res.status(401).send('invalid signature');
    }

    const event = JSON.parse(req.body);
    const { candidate_email } = event.data.arguments;
    const cv = await ats.getCandidateByEmail(candidate_email);

    // Whatever you return is fed back to the LLM as the tool result
    res.json({
      name: cv.fullName,
      headline: cv.headline,
      years_experience: cv.yearsExperience,
      last_role: cv.experience[0],
      skills: cv.skills.slice(0, 10),
    });
  }
);
```

Now when the candidate joins, the AI interviewer's first turn can be:
*"Hi Jane — I see you've been a senior backend engineer at Acme for three
years. I'd love to dig into the payments-platform migration on your CV. Can
you walk me through it?"*

### Frontend

```html theme={null}
<!-- Embed the meeting room -->
<iframe
  id="meeting-frame"
  allow="camera; microphone; autoplay; display-capture"
  style="width: 100%; height: 600px; border: none; border-radius: 12px;"
></iframe>

<script>
async function startSession() {
  const res = await fetch('/api/start-session', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      candidateName: 'Jane Smith',
      scenarioId: 'your-scenario-uuid',
    }),
  });
  const { roomUrl, token } = await res.json();
  document.getElementById('meeting-frame').src = `${roomUrl}?t=${token}`;
}
</script>
```

<Tip>
  The tool definition is account-scoped — register `lookup_candidate` once and
  attach it to every interview scenario you ship. Rotating the webhook secret
  or updating the JSON Schema propagates to every attached scenario instantly.
  See the [Tools guide](/api-reference/custom-functions) for the full CRUD
  surface.
</Tip>

***

## Example 2: Batch Assessment Pipeline

Run assessments for multiple candidates and collect results in a spreadsheet.

```python theme={null}
import os
import time
import csv
import requests

API = "https://api.waterr.ai/v1"
TOKEN = os.environ["WATERR_API_TOKEN"]
headers = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

SCENARIO_ID = "your-scenario-uuid"

candidates = [
    {"name": "Alice Johnson", "email": "alice@example.com"},
    {"name": "Bob Chen", "email": "bob@example.com"},
    {"name": "Carol Davis", "email": "carol@example.com"},
]

# Step 1: Create meetings for each candidate
meetings = []
for candidate in candidates:
    res = requests.post(f"{API}/meetings", headers=headers, json={
        "person_name": candidate["name"],
        "scenario_id": SCENARIO_ID,
    })
    meeting = res.json()["data"]
    meetings.append({
        **candidate,
        "meeting_id": meeting["id"],
        "join_url": meeting["daily_meeting_url"],
    })
    print(f"Created session for {candidate['name']}: {meeting['daily_meeting_url']}")

# Step 2: Send join links to candidates (via your email system)
# ... send meetings[i]["join_url"] to each candidate ...

# Step 3: After sessions complete, collect results
time.sleep(60)  # Wait for analysis to generate

results = []
for m in meetings:
    res = requests.get(f"{API}/analyses/meeting/{m['meeting_id']}", headers=headers)
    if res.ok:
        analysis = res.json()
        results.append({
            "name": m["name"],
            "email": m["email"],
            "score": analysis.get("average_score", "N/A"),
            "strengths": "; ".join(analysis.get("strengths", [])),
            "growth_areas": "; ".join(analysis.get("growth_areas", [])),
        })

# Step 4: Export to CSV
with open("assessment_results.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "email", "score", "strengths", "growth_areas"])
    writer.writeheader()
    writer.writerows(results)

print(f"Exported {len(results)} results to assessment_results.csv")
```

***

## Example 3: Webhook-Driven Analytics Dashboard

Receive results in real-time via webhook and store them in your database.

### Webhook receiver (Express)

```javascript theme={null}
const express = require('express');
const app = express();
app.use(express.json());

// Receive webhook when a session completes
app.post('/webhooks/waterr', async (req, res) => {
  const { event, data } = req.body;

  if (event === 'meeting.completed') {
    // Store in your database
    await db.sessionResults.create({
      meetingId: data.meeting_id,
      scenarioId: data.scenario_id,
      participantName: data.person_name,
      score: data.goals?.reduce((sum, g) => sum + g.score, 0) / data.goals?.length,
      goals: data.goals,
      completedAt: data.completed_at,
    });

    // Notify your team
    await slack.send(`Session completed: ${data.person_name} scored ${avgScore}/10`);
  }

  res.sendStatus(200); // Always respond 200 to acknowledge receipt
});
```

### Register the webhook

```bash theme={null}
curl -X PUT https://api.waterr.ai/v1/scenarios/{scenarioId}/session-options \
  -H "Authorization: Bearer wai_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://your-app.com/webhooks/waterr"
  }'
```

<Warning>
  Always respond with a `2xx` status to webhook requests, even if you encounter an error processing the data. Non-2xx responses trigger retries, which can lead to duplicate processing.
</Warning>
