Back to Home

Developer Documentation

Integrate Naoma AI demo sessions into your website with the SDK, or access session data programmatically via the REST API.

SDK

Embed AI-powered demo sessions with the JavaScript SDK.

Installation

Add this snippet to every page where you want to start a demo session. Replace YOUR_CLIENT_ID with your unique client identifier.

<script>
  window.NaomaConfig = {
    clientId: 'YOUR_CLIENT_ID',
    // recordingNotice: 'This session is being recorded' // optional
  };
  (function(d,s,id){
    var js,fjs=d.getElementsByTagName(s)[0];
    if(d.getElementById(id))return;
    js=d.createElement(s);js.id=id;
    js.src='https://demoagent.naoma.app/sdk/sdk.js';
    fjs.parentNode.insertBefore(js,fjs);
  })(document,'script','naoma-sdk');
</script>

Configuration Options

ParameterTypeDescription
clientIdrequiredstringYour unique client identifier.
recordingNoticestringCustom recording notice text to display in the demo session.

API Reference

Naoma.startDemo(config)

Opens the demo agent in a modal overlay.

ParameterTypeDescription
config.metadataobjectData from the user session to pass to the demo (e.g., user ID, name, email, plan). Merged over metadata set via Naoma.setMetadata(). The former name sessionData still works as a deprecated alias.
config.languagestringLanguage code for the demo session (e.g., "en", "es", "de"). If not provided, the visitor's browser languages are matched against the agent's configured languages, falling back to the agent's default.
The email metadata key is special. The address under metadata.email becomes the session's email field: Naoma verifies its domain (email_verified in session data and webhook payloads) and, when follow-up emails are enabled for your account, sends the prospect a conversation summary to it. Other keys are stored and passed through as-is.
// Start demo with default settings
Naoma.startDemo();

// Start demo in Spanish
Naoma.startDemo({ language: 'es' });

// Start demo with metadata
Naoma.startDemo({
  metadata: {
    userId: '12345',
    name: 'John Doe',
    email: 'john@example.com',
    plan: 'premium'
  }
});

// Start demo with metadata and language
Naoma.startDemo({
  metadata: { userId: '12345', name: 'John Doe' },
  language: 'de'
});

// Or set metadata once for all demos started on the page
Naoma.setMetadata({ userId: '12345', email: 'john@example.com' });
Naoma.startDemo();

Naoma.trackButtonView(element)

Tracks when a button becomes visible in the viewport.

ParameterTypeDescription
elementrequiredHTMLElementThe button element to track.
const btn = document.getElementById("start-demo-btn");
Naoma.trackButtonView(btn);

Naoma.getLanguages()

Returns the list of languages supported by the agent, sorted by the user's browser language preferences. Languages matching the user's preferences appear first.

Returns: Promise<string[]> β€” Array of language codes

const languages = await Naoma.getLanguages();
console.log(languages); // ["en", "es", "de"]

// Build a language selector
if (languages.length > 1) {
  const select = document.createElement('select');
  languages.forEach(lang => {
    const option = document.createElement('option');
    option.value = lang;
    option.textContent = lang.toUpperCase();
    select.appendChild(option);
  });

  select.onchange = () => Naoma.startDemo({ language: select.value });
}

Naoma.checkClient()

Performs pre-flight checks to determine if the client is eligible to run demo sessions. Use this to conditionally show or hide demo UI elements.

Returns: Promise<{allowed: boolean, reason: string|null}>

const { allowed, reason } = await Naoma.checkClient();

if (allowed) {
  document.getElementById('demo-button').style.display = 'block';
} else {
  console.log('Demo not available:', reason);
}

Widget API

The widget provides an interactive UI element that can be embedded on your site to offer demo experiences.

Naoma.widget.configure(config)

Configures the widget's position and data collection behavior.

ParameterTypeDescription
config.positionstringWidget position. Options: "top-left", "top-right", "bottom-left", "bottom-right", "top-center", "bottom-center", "left-center", "right-center". Default: "bottom-right".
config.ctaTextstringCustom text for the CTA button. Default: "Talk to an agent".
config.primaryColorstringPrimary color for buttons and input focus border. Replaces the default gradient with a solid color. e.g., "#0066FF".
config.textColorstringText color for buttons. Default: "white". e.g., "#FFFFFF".
config.collectDataobjectConfiguration for collecting additional user data before starting the demo.
collectData.dataKeyrequiredstringThe key under which the collected value is added to session metadata. Use "email" when collecting the prospect's email address β€” that key powers email verification and follow-up emails (see startDemo above).
collectData.placeholderstringPlaceholder text for the input field. Default: "Enter your email".
collectData.formTitlestringTitle text displayed above the input field. Default: "Start your demo".
collectData.validatorfunctionValidation function that receives the input value and returns true if valid, or an error message string if invalid.
Rendering: configure() only stores the configuration β€” the widget appears once you call Naoma.widget.show().
Widget state persistence: The widget remembers if the user collapsed it. On subsequent page loads, it remains collapsed until the user expands it again (stored in localStorage).
Mobile responsiveness: On viewports ≀ 480px, the data collection form automatically switches to a vertical layout.
// Position widget with email collection
Naoma.widget.configure({
  position: 'bottom-left',
  collectData: {
    dataKey: 'email',
    formTitle: 'Get started today',
    placeholder: 'Enter your email',
    validator: (value) => value && value.includes('@') || 'Valid email required'
  }
});
Naoma.widget.show();

// Position widget with custom colors
Naoma.widget.configure({
  position: 'top-right',
  ctaText: 'Start a demo',
  primaryColor: '#0066FF',
  textColor: '#FFFFFF'
});
Naoma.widget.show();

Naoma.widget.show() / Naoma.widget.hide()

Display or hide the widget on the page.

Naoma.widget.show();
Naoma.widget.hide();

Naoma.widget.expand() / Naoma.widget.collapse()

Expand the widget to show the full interface, or collapse it to its minimal state.

Naoma.widget.expand();
Naoma.widget.collapse();

Hero API

The hero block embeds a large animated avatar directly into your page β€” ideal for landing page hero sections. A placeholder image is shown instantly while the animation loads.

Naoma.hero.mount(container, config)

Mounts the hero block into the given container element.

ParameterTypeDescription
containerrequiredHTMLElementThe DOM element to mount the hero into.
config.widthnumberWidth of the avatar area in pixels. Default: fills the container.
config.heightnumberHeight of the avatar area in pixels. Default: fills the container.
config.ctaTextstringCustom text for the CTA button. Default: "Start demo now".
config.primaryColorstringPrimary color for buttons and form elements. e.g., "#7C3AED".
config.textColorstringText color for buttons. Default: "white".
config.collectDataobjectConfiguration for collecting user data before starting the demo (same shape as widget’s collectData).
collectData.dataKeyrequiredstringThe key under which the collected value is added to session metadata. Use "email" when collecting the prospect's email address β€” that key powers email verification and follow-up emails (see startDemo above).
collectData.placeholderstringPlaceholder text for the input field. Default: "Enter your email".
collectData.formTitlestringTitle text displayed above the input field.
collectData.validatorfunctionValidation function that returns true if valid, or an error message string if invalid.
Metadata: The hero automatically merges metadata set via Naoma.setMetadata() with any data collected via the form. You can pre-set known user data (name, phone, etc.) and the hero will include it when starting the demo.
Styling: All hero elements use .naoma-hero-* CSS classes that you can override with your own styles.
// Simple hero, click CTA to start demo immediately
Naoma.hero.mount(document.getElementById('hero-container'), {
  width: 500,
  height: 500,
  ctaText: 'Start demo now',
  primaryColor: '#7C3AED'
});

// Hero with email collection
Naoma.hero.mount(document.getElementById('hero-container'), {
  ctaText: 'Try it now',
  primaryColor: '#4f46e5',
  collectData: {
    dataKey: 'email',
    placeholder: 'Enter your work email',
    formTitle: 'Get a personalized demo',
    validator: (value) => {
      if (!value || !value.includes('@')) return 'Please enter a valid email';
      return true;
    }
  }
});

// Pre-set metadata, then mount hero
Naoma.setMetadata({ username: 'John', phone: '+1234567890' });
Naoma.hero.mount(document.getElementById('hero-container'));

Naoma.hero.unmount()

Removes the hero block from the page and cleans up resources.

Naoma.hero.unmount();

Complete Example

A full page example using the demo button, hero block, and widget.

<!doctype html>
<html>
<head>
  <script>
    window.NaomaConfig = {
      clientId: 'YOUR_CLIENT_ID'
    };
    (function(d,s,id){
      var js,fjs=d.getElementsByTagName(s)[0];
      if(d.getElementById(id))return;
      js=d.createElement(s);js.id=id;
      js.src='https://demoagent.naoma.app/sdk/sdk.js';
      fjs.parentNode.insertBefore(js,fjs);
    })(document,'script','naoma-sdk');
  </script>
</head>
<body>
  <div id="demo-section">
    <button onclick="startDemo()" id="startDemoButton">Get a demo now!</button>
  </div>

  <script>
    // === OPTION 1: Demo Button ===
    // Track button view (optional)
    Naoma.trackButtonView(document.querySelector("#startDemoButton"));

    // Start demo on button click with metadata
    function startDemo() {
      Naoma.startDemo({
        metadata: {
          name: "John Doe",
          company: "Acme Inc",
          userId: "12345"
        }
      });
    }

    // === OPTION 2: Hero Block ===
    // Mount an animated avatar hero with email collection
    Naoma.hero.mount(document.getElementById('demo-section'), {
      width: 400,
      height: 400,
      ctaText: 'Start demo now',
      primaryColor: '#7C3AED',
      collectData: {
        dataKey: 'email',
        placeholder: 'Enter your work email',
        validator: (value) => value && value.includes('@') || 'Valid email required'
      }
    });

    // === OPTION 3: Widget ===
    // Configure widget position and email collection, then display it
    Naoma.widget.configure({
      position: 'bottom-right',
      ctaText: 'Start a demo',
      primaryColor: '#7C3AED',
      collectData: {
        dataKey: 'email',
        formTitle: 'Get started today',
        placeholder: 'Enter your email',
        validator: (value) => value && value.includes('@') || 'Valid email required'
      }
    });
    Naoma.widget.show();
  </script>
</body>
</html>

Public API

Access session data and receive real-time event notifications via the REST API.

Base URL: https://api.naoma.app

Authentication

All API requests require authentication via one of two methods. To obtain your credentials, contact the Naoma team.

Bearer Token

Authorization: Bearer <token>

HTTP Basic Auth

Where client_id is your account's client ID and token is your API token.

Authorization: Basic <base64(client_id:token)>
Tip: Basic Auth is useful for manual exports β€” you can open a download link directly in your browser and enter your client_id and token when prompted. For example: https://api.naoma.app/v1/sessions.csv

List Sessions

Returns your account's sessions, ordered oldest first. Test sessions are excluded.

GET /v1/sessions.json

Returns sessions as a JSON array. A key is present only when it was selected via fields and has a value β€” the response never contains nulls. The transcript is filtered to user and assistant turns.

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "metadata": {"email": "jane@acme.com", "utm_source": "linkedin"},
      "transcript": [
        {"role": "assistant", "content": "Hi! I'm Naoma. What brings you here today?", "audio_offset_ms": 0},
        {"role": "user", "content": "We're looking at AI for our sales team.", "audio_offset_ms": 5230},
        {"role": "assistant", "content": "Great β€” how large is the team?", "audio_offset_ms": 9100}
      ],
      "summary": "Jane from Acme evaluated Naoma for a 25-person sales team.",
      "evaluation": {
        "language": "en",
        "customer_name": "Jane Doe",
        "qualification": {
          "status": "qualified",
          "reasoning": "Jane manages a 25-person sales team and confirmed budget approval."
        },
        "questions": {
          "team_size": {"was_asked": true, "was_answered": true, "answer": "About 25 people."}
        },
        "insights": {
          "buying_timeline": {"value": "Decision expected this quarter", "evidence": "We want something running before the end of Q3."}
        },
        "prospect_questions": [
          {"question": "Does it integrate with HubSpot?", "answer": "Yes, via webhooks."}
        ],
        "objections": [],
        "prospect_summary": "Thanks for the chat, Jane! We covered your team setup and booked a demo.",
        "summary": "Jane Doe evaluated Naoma for inbound qualification; qualified with a Q3 timeline."
      },
      "email": "jane@acme.com",
      "email_verified": true,
      "duration_secs": 184,
      "created_at": "2026-07-14T10:30:00.123456Z"
    }
  ]
}

qualification.status is one of qualified, disqualified, not_enough_data. The questions and insights keys are the ones configured on your agent. email is the prospect's address collected during the session, normalized; email_verified reports the domain-level check β€” true when the domain accepts mail (not that the mailbox exists), false when it does not, and the key is omitted when no email was collected or the session is not yet evaluated.

GET /v1/sessions.csv

Returns a CSV file with a header row followed by data rows for the selected fields. Empty cells stand for missing values; email_verified is true, false, or empty.

Query Parameters

ParameterTypeDescription
sinceint64Unix timestamp. Returns only sessions created after this time. Default: 0.
limitintMax number of sessions to return (1–100). Values above 100 are clamped to 100. Default: 100.
fieldsstringComma-separated list of fields to include. Available: id, metadata, transcript, summary, evaluation, email, email_verified, duration_secs, created_at. Unknown names are ignored. Default: all.

Example:

GET /v1/sessions.json?since=1700000000&limit=50&fields=id,summary,created_at

Pagination

Results are ordered by created_at ascending. To paginate, use the created_at of the last returned item (as a unix timestamp) as the since value in the next request. Because since has whole-second granularity, the next page can repeat the previous page's last items β€” dedupe by id.

Errors

StatusDescription
400Invalid query parameter.
401Missing or invalid credentials.

Webhooks

Webhooks allow you to receive real-time notifications when events occur. To register a webhook, contact the Naoma team with the URL you'd like to receive events at. You will receive a webhook secret for signature verification.

Events

StatusDescription
session.evaluatedFired every time a session's evaluation is written, including re-evaluations of the same session. Test sessions and failed evaluations never fire.

Payload

Webhook events are delivered as POST requests with a JSON body. data is the full session object: every field is always present, with null when unset, and the transcript is unfiltered β€” it includes the agent's system message and tool activity alongside the spoken turns. The evaluation object has the same shape as in List Sessions.

{
  "event_type": "session.evaluated",
  "timestamp": "2026-07-14T10:30:05.123456789Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "account_id": "0d4f6f10-3f9e-4b21-9d2a-6a1b2c3d4e5f",
    "account_name": "Acme Inc",
    "client_id": "acme",
    "agent_id": "naoma_agent_4fz2k9qj1m8p7w3x",
    "agent_name": "Acme Discovery Agent",
    "metadata": {"email": "jane@acme.com", "utm_source": "linkedin"},
    "referrer": "https://acme.com/pricing",
    "is_test": false,
    "draft": false,
    "status": "processed",
    "engine": "conva",
    "external_conversation_id": null,
    "transcript": [
      {"role": "system", "content": "You are Acme's discovery agent..."},
      {"role": "assistant", "content": "Hi! I'm Naoma. What brings you here today?", "audio_offset_ms": 0},
      {"role": "user", "content": "We're looking at AI for our sales team.", "audio_offset_ms": 5230},
      {"role": "tool_call", "tool_call": {"id": "call_1", "name": "open_email_collection", "args": {}}, "audio_offset_ms": 9100},
      {"role": "tool_result", "tool_result": {"tool_call_id": "call_1", "result": {"status": "opened"}}, "audio_offset_ms": 9100},
      {"role": "assistant", "content": "Great β€” how large is the team?", "audio_offset_ms": 9400}
    ],
    "summary": "Jane from Acme evaluated Naoma for a 25-person sales team.",
    "evaluation": {"...": "same shape as in List Sessions"},
    "evaluation_error": null,
    "duration_secs": 184,
    "email": "jane@acme.com",
    "email_verified": true,
    "runtime_config_version_id": "7c1d2e30-aa11-4bde-8f00-112233445566",
    "runtime_config": {"...": "the agent configuration the session ran on"},
    "created_at": "2026-07-14T10:27:00.481920Z",
    "updated_at": "2026-07-14T10:30:04.912345Z"
  }
}
Delivery: your endpoint should respond with a 2xx status within 10 seconds; any other outcome is retried up to 3 attempts, ~30 seconds apart. Re-evaluations re-deliver the session with its updated evaluation, so handle deliveries idempotently by session id.

Signature Verification

Each request includes an X-Webhook-Signature header containing an HMAC-SHA256 hex digest of the request body, signed with your webhook secret. Verification is optional but recommended.

  1. Compute HMAC-SHA256 of the raw request body using your secret as the key.
  2. Hex-encode the result.
  3. Compare it to the X-Webhook-Signature header value.
const crypto = require('crypto');

function verifyWebhookSignature(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return signature === expected;
}

Need Help?

Our team can help you integrate Naoma into your website and get the most out of AI-powered demos.

Talk to Sales Team