SYNCLI DEVELOPER PORTAL

Standalone Voice + API Console Β· Exact same flow as main.js
Click Connect to start voice session...
new
new
Send a message to start...

🎭 Personas

No personas yet. Create one below and it will be used in your next voice session.

This is what the LLM sees at the start of every voice session. Changes take effect on the next Connect.

🎬 Avatar (MuseTalk)

Lip-sync avatar selection. Currently disabled due to GPU constraints.

No avatars yet. Upload or select a default.
Select an avatar to preview
Unknown
None

πŸ“š Knowledge Base

Loading...

Extract routes works for any app URL (Playwright). Use Scrape docs for documentation text.

Advanced: App.tsx source (optional)

Scraped text is injected into the system prompt after persona but before learning layer.

πŸ”§ Tool Execution Log

All tool calls made by the agent during your sessions. Refreshes automatically.

Click Refresh to load tool history...
Click Send to see response...

πŸ“§ SMTP CONFIGURATION

Configure how your voice assistant sends notification emails. Choose between Syncli's metered default server or connect your custom server.

Ready to verify connectivity...
0
Emails Dispatched
$0.00
Metered Accrual Cost
No email transactions logged yet.

🎨 EMAIL TEMPLATE DESIGNER

Create and customize highly responsive email layouts using the BeeFree drag-and-drop designer. Designed templates are stored in your private workspace, automatically loaded by your voice agent during workflow dispatches.

BEEFREE SDK INTEGRATION

Not Configured

Requires a BeeFree developer subscription. Sign up for free →

βž• NEW TEMPLATE GENERATOR

No custom templates found. Build one to get started!

πŸ“Š AI VOICE INTELLIGENCE DASHBOARD

Real-time conversation outcomes, intent tracking, action item extraction, and call quality telemetry.

πŸŽ™οΈ
0
-- vs last period
βœ…
0%
-- vs last period
πŸ‘€
0%
-- handoff frequency
⏱️
0s
-- active conversation time
❀️
0%
Weighted sentiment index
No topics extracted yet
No intents identified yet
No questions clustered yet
Conversation Balance
AI: 50%
User: 50%
Balance avoids bot monologue.
Interruptions: 0
Clarifications: 0
Friction score: Low (0.0)
0ms
Avg response delay: 0ms
--
Resolution: --% successfully resolved
0 tasks
No pending action items
No user timelines loaded
Session ID Caller Key Sentiment Duration Resolution Outcome reason Actions

πŸ›‘οΈ CONVERSATIONAL RATE LIMITS & ANTI-EXPLOITATION

Prevent bot exploitation, rapid coordinate spamming, and API budget drain. Establish customizable tracking rules and set dynamic conversational duration parameters for external end-users.

βš™οΈ RATE CAP POLICIES

Decides how callers are identified for speaking duration allocation. Modes that include Browser Session require your frontend to send end_user_session_id on POST /start β€” see Quick Ref β†’ End-user session ID.

Loading real-time usage statistics...

πŸ”Œ POST-CALL CRM & WEBHOOK INTEGRATION

Automatically stream structured summaries, conversation intelligence metrics, customer sentiments, extracted key topics, action items, and complete dialogue transcripts directly to your internal backend endpoints (e.g. Salesforce, HubSpot, or custom QA/CRM database servers) as soon as a voice session terminates.

βš™οΈ WEBHOOK TARGET CONFIGURATION

The target HTTPS POST URL where call-summary payloads will be delivered.

If defined, we will send this token in the Authorization: Bearer <Token> HTTP header.

πŸ“‘ Enter an Endpoint and click "Test Webhook" to execute a live connection handshake. Detailed diagnostics, response payloads, and HTTP status metrics will populate here.

πŸ“– WEBHOOK SPECIFICATION & POSTMAN INTEGRATION REFERENCE

To integrate Syncli with your system, you must host a listener endpoint that accepts HTTP POST requests with application/json payloads. Below is the complete delivery schema and a copy-pasteable Postman Collection.

πŸ“‘ HOW THE REQUEST IS CONSTRUCTED
  • Method: Delivered strictly via HTTP POST.
  • Headers: Includes Content-Type: application/json and optional authorization token: Authorization: Bearer <Token>.
  • Delivery: Sent during the voice agent teardown path after the call ends, before per-session context is released, so the receiver gets the complete transcript and analytics payload.
  • Timeout: The receiver should acknowledge quickly. Webhook delivery is awaited with a bounded timeout, and slow endpoints may be marked as failed in Syncli logs.
  • End-state routing: Use session.termination_reason with session.ended_by. Known reasons include user_ended_call, network_disconnect, agent_duration_limit, agent_allocation_limit, transferred_to_human, and session_ended.
  • Schema guarantees: The v2.0 payload always includes session, developer, user_profile, client_context, analytics, quality_metrics, transcript, and metadata. Optional unknown values are sent as null, empty arrays, or documented defaults.
POST /webhooks/voice-summary HTTP/1.1
Host: api.yourdomain.com
Content-Type: application/json
Authorization: Bearer your_bearer_secret_token
User-Agent: SyncliVoiceGateway/6.1 (httpx/0.24.1)
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: <calculated>

{
  "event": "voice_agent.session_completed",
  "event_version": "2.0",
  "timestamp": 1779556480,
  "session": {
    "session_id": "call_8f3d10ab_cd39_4f0b",
    "started_at": 1779556296,
    "ended_at": 1779556480,
    "duration_seconds": 184,
    "completed_successfully": true,
    "resolution_status": "resolved_by_ai",
    "termination_reason": "user_ended_call",
    "ended_by": "user",
    "was_transferred_to_human": false,
    "handoff_reason": null
  },
  "developer": {
    "developer_user_id": "dev_usr_0a12",
    "workspace_id": "wrk_123",
    "project_id": "proj_456",
    "resolved_tracking_key": "[email protected]"
  },
  "user_profile": {
    "user_id": "usr_99b0c2a1",
    "name": "Obiajulu",
    "email": "[email protected]",
    "phone": null,
    "client_ip": "192.168.1.45",
    "is_returning_user": true
  },
  "client_context": {
    "device_type": "desktop",
    "browser": "chrome",
    "os": "macos",
    "location": {
      "country": "US",
      "region": null,
      "city": null,
      "is_vpn_or_local": true
    },
    "user_agent": "Mozilla/5.0..."
  },
  "analytics": {
    "overall_sentiment": "Positive / Inquisitive",
    "sentiment_score": 0.82,
    "conversation_summary": "The user inquired about Kanban boards and smart attendance. Zara explained board configurations and attendees.",
    "key_topics_extracted": ["Kanban", "Attendance"],
    "detected_intents": ["product_question", "setup_guidance"],
    "faq_candidates": [
      "How do I configure Kanban boards?",
      "How does smart attendance work?"
    ],
    "action_items": [
      {
        "owner": "user",
        "task": "Configure smart attendance via video meetings",
        "priority": "medium",
        "due_date": null,
        "status": "open"
      }
    ]
  },
  "quality_metrics": {
    "ai_resolution_score": 0.91,
    "customer_satisfaction_estimate": 0.88,
    "conversation_friction_score": 0.12,
    "first_response_time_ms": 900,
    "avg_response_time_ms": 1200,
    "assistant_talk_ratio": 0.58,
    "user_talk_ratio": 0.42,
    "interruption_count": 0,
    "clarification_count": 1,
    "fallback_count": 0
  },
  "transcript": [
    {
      "speaker": "assistant",
      "text": "Good afternoon, OBI!",
      "timestamp": 1779556296
    },
    {
      "speaker": "user",
      "text": "Can you explain Kanban boards?",
      "timestamp": 1779556304
    }
  ],
  "metadata": {
    "agent_id": "agent_zara",
    "agent_name": "Zara",
    "agent_version": "6.1",
    "prompt_version": "2026-05-voice-v3",
    "source": "SyncliVoiceGateway",
    "environment": "production"
  }
}
            

Copy this JSON schema, save it as a file named syncli_webhook.json, and import it directly into Postman to instantly test and debug your webhook receiver.

{
  "info": {
    "name": "Syncli Webhook Mocking Collection",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Mock Webhook Trigger",
      "request": {
        "method": "POST",
        "header": [
          { "key": "Content-Type", "value": "application/json" },
          { "key": "Authorization", "value": "Bearer {{YOUR_WEBHOOK_SECRET_TOKEN}}" }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"event\": \"voice_agent.session_completed\",\n  \"event_version\": \"2.0\",\n  \"timestamp\": 1779556480,\n  \"session\": {\n    \"session_id\": \"call_8f3d10ab_cd39_4f0b\",\n    \"started_at\": 1779556296,\n    \"ended_at\": 1779556480,\n    \"duration_seconds\": 184,\n    \"completed_successfully\": true,\n    \"resolution_status\": \"resolved_by_ai\",\n    \"termination_reason\": \"user_ended_call\",\n    \"ended_by\": \"user\",\n    \"was_transferred_to_human\": false,\n    \"handoff_reason\": null\n  },\n  \"developer\": {\n    \"developer_user_id\": \"dev_usr_0a12\",\n    \"workspace_id\": \"wrk_123\",\n    \"project_id\": \"proj_456\",\n    \"resolved_tracking_key\": \"[email protected]\"\n  },\n  \"user_profile\": {\n    \"user_id\": \"usr_99b0c2a1\",\n    \"name\": \"Obiajulu\",\n    \"email\": \"[email protected]\",\n    \"phone\": null,\n    \"client_ip\": \"192.168.1.45\",\n    \"is_returning_user\": true\n  },\n  \"client_context\": {\n    \"device_type\": \"desktop\",\n    \"browser\": \"chrome\",\n    \"os\": \"macos\",\n    \"location\": {\n      \"country\": \"US\",\n      \"region\": null,\n      \"city\": null,\n      \"is_vpn_or_local\": true\n    },\n    \"user_agent\": \"Mozilla/5.0...\"\n  },\n  \"analytics\": {\n    \"overall_sentiment\": \"Positive / Inquisitive\",\n    \"sentiment_score\": 0.82,\n    \"conversation_summary\": \"The user inquired about Kanban boards and smart attendance. Zara explained board configurations and attendees.\",\n    \"key_topics_extracted\": [\"Kanban\", \"Attendance\"],\n    \"detected_intents\": [\"product_question\", \"setup_guidance\"],\n    \"faq_candidates\": [\n      \"How do I configure Kanban boards?\",\n      \"How does smart attendance work?\"\n    ],\n    \"action_items\": [\n      {\n        \"owner\": \"user\",\n        \"task\": \"Configure smart attendance via video meetings\",\n        \"priority\": \"medium\",\n        \"due_date\": null,\n        \"status\": \"open\"\n      }\n    ]\n  },\n  \"quality_metrics\": {\n    \"ai_resolution_score\": 0.91,\n    \"customer_satisfaction_estimate\": 0.88,\n    \"conversation_friction_score\": 0.12,\n    \"first_response_time_ms\": 900,\n    \"avg_response_time_ms\": 1200,\n    \"assistant_talk_ratio\": 0.58,\n    \"user_talk_ratio\": 0.42,\n    \"interruption_count\": 0,\n    \"clarification_count\": 1,\n    \"fallback_count\": 0\n  },\n  \"transcript\": [\n    {\n      \"speaker\": \"assistant\",\n      \"text\": \"Good afternoon, OBI!\",\n      \"timestamp\": 1779556296\n    },\n    {\n      \"speaker\": \"user\",\n      \"text\": \"Can you explain Kanban boards?\",\n      \"timestamp\": 1779556304\n    }\n  ],\n  \"metadata\": {\n    \"agent_id\": \"agent_zara\",\n    \"agent_name\": \"Zara\",\n    \"agent_version\": \"6.1\",\n    \"prompt_version\": \"2026-05-voice-v3\",\n    \"source\": \"SyncliVoiceGateway\",\n    \"environment\": \"production\"\n  }\n}"
        },
        "url": {
          "raw": "https://yourdomain.com/webhooks/voice-summary"
        }
      }
    }
  ]
}
            

Quick Reference

Authentication

Authorization: Bearer syncli_...
      

Accepts Session JWT, syncli_... API keys, or legacy sk-... keys.

Base URL

https://apivoice.syncli.cloud/v1
      

HTTP API

GET  /v1/models                        # List available models
POST /v1/chat/completions            # Text chat completion
GET  /api/v1/api-keys                # Manage API keys
POST /api/v1/api-keys
GET  /v1/mcp/servers               # MCP server config
POST /v1/mcp/servers
DELETE /v1/mcp/servers/{id}
      

Chat Completion Example

POST /v1/chat/completions
{
  "model": "syncli-voice",
  "messages": [{"role": "user", "content": "Hello!"}],
  "stream": false
}
      

Voice (WebRTC)

Step 1: POST /start
  Authorization: Bearer syncli_...
  Body {
    end_user_session_id,   // required for API embeds β€” see section below
    chat_id,               // optional β€” reuse from prior /start response
    end_user_email,        // optional β€” when tracking mode uses email
    client_timezone,
    client_timestamp,
    agent_config           // optional β€” per-session agent behaviour (see below)
  }
  Response {
    sessionId,             // use in Step 5 URL
    chat_id,               // persist in localStorage for return visits
    iceServers[],
    iceTransportPolicy
  }

Step 2: Create RTCPeerConnection
  pc = new RTCPeerConnection({
    iceServers,
    iceTransportPolicy: 'relay'
  })

Step 3: Attach mic audio (full-duplex β€” keep mic open while bot speaks)
  // Retrieve mobile-optimized constraints via SyncliVoiceAudio helper
  const voiceConstraints = SyncliVoiceAudio.getVoiceAudioConstraints()
  localStream = await getUserMedia(voiceConstraints)
  const transceiver = pc.addTransceiver('audio', { direction: 'sendrecv' })
  await transceiver.sender.replaceTrack(localStream.getAudioTracks()[0])
  // Play remote bot audio (pc.ontrack β†’ audio element) for browser AEC
  // Do NOT mute mic on bot-started-speaking β€” barge-in needs continuous uplink

Step 4: Create data channel & AEC warmup notifier
  const dc = pc.createDataChannel('chat', { ordered: true })
  const aecWarmup = SyncliVoiceAudio.createAecWarmupNotifier(dc)
  dc.onopen = () => { if (aecWarmup) aecWarmup.onDataChannelOpen(); }
  // On pc.ontrack play remote audio track, trigger:
  // audioEl.play().then(() => { if (aecWarmup) aecWarmup.onRemoteAudioPlaying(); })
  // Chat UI: ignore bot-output; display bot-output-raw only

Step 5: Offer β†’ Answer exchange
  offer  = await pc.createOffer()
  await pc.setLocalDescription(offer)
  answer = await POST /sessions/{sessionId}/api/offer
    Body {
      sdp, type,
      request_data: {
        end_user_session_id,  // same value as /start
        chat_id,
        client_timezone,
        client_timestamp
      }
    }
  await pc.setRemoteDescription(
    new RTCSessionDescription(answer)
  )

Step 6: ICE candidates
  Buffer candidates until pc_id is known,
  then PATCH /sessions/{id}/api/offer
  Body: { pc_id, candidates: [...] }
      

πŸ‘€ End-user session ID (embedded voice)

When you embed Syncli voice on your website with an API key, every visitor must be identified by a stable end_user_session_id that your frontend generates and sends on POST /start. This is separate from the gateway sessionId returned in the response (that ID is per WebRTC call).

Without end_user_session_id, the gateway falls back to the client IP address. Multiple visitors behind the same NAT or corporate network would share one caller profile, chat history, rate-limit bucket, and webhook identity.

What it enables

1. Generate and persist a browser session ID

Create a UUID once per browser profile and store it in localStorage (or your own auth/session store). Reuse the same value on every call and reconnect from that browser.

function getBrowserSessionId() {
  const key = 'syncli_browser_session_id';
  let id = localStorage.getItem(key);
  if (!id) {
    id = (crypto.randomUUID && crypto.randomUUID())
      || ('bs_' + Date.now() + '_' + Math.random().toString(36).slice(2));
    localStorage.setItem(key, id);
  }
  return id;
}
      

If your app already has a logged-in user, you may use your own stable ID instead (e.g. user_abc123 or a session token), as long as it is unique per end user and consistent across visits.

2. Send on POST /start

const startRes = await fetch('https://apivoice.syncli.cloud/start', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer syncli_YOUR_API_KEY'
  },
  body: JSON.stringify({
    end_user_session_id: getBrowserSessionId(),
    chat_id: localStorage.getItem('syncli_voice_chat_id') || null,
    end_user_email: loggedInUser?.email || null,  // optional
    client_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    client_timestamp: new Date().toISOString()
  })
});

const startData = await startRes.json();
// Persist chat_id for the next visit / reconnect
if (startData.chat_id) {
  localStorage.setItem('syncli_voice_chat_id', startData.chat_id);
}
const { sessionId, iceServers, iceTransportPolicy } = startData;
      

3. Send again in the WebRTC offer

Include the same end_user_session_id (and persisted chat_id) inside request_data when posting the SDP offer. This keeps identity aligned if the client reconnects mid-call.

await fetch(`https://apivoice.syncli.cloud/sessions/${sessionId}/api/offer`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer syncli_YOUR_API_KEY'
  },
  body: JSON.stringify({
    sdp: pc.localDescription.sdp,
    type: pc.localDescription.type,
    request_data: {
      end_user_session_id: getBrowserSessionId(),
      chat_id: startData.chat_id || localStorage.getItem('syncli_voice_chat_id'),
      client_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
      client_timestamp: new Date().toISOString()
    }
  })
});
      

Request fields reference

end_user_session_id   string   Required for API embeds. Stable per-browser or per-user ID from your frontend.
chat_id               string   Optional. Omit on first visit; send value returned by prior /start to resume history.
end_user_email        string   Optional. Used when tracking mode includes email (email, email_and_browser, etc.).
client_timezone       string   IANA timezone, e.g. "America/New_York".
client_timestamp      string   ISO-8601 timestamp from the client clock.
      

πŸ’‘ Reference implementation: The Voice tab in this portal (developer-portal/js/webrtc.js) implements the full flow with getBrowserSessionId(), /start, offer request_data, and full-duplex mic handling. Copy that pattern into your embed. Full guide: Voice Integration β€” Full-Duplex & Barge-In.

πŸ”Š Full-Duplex & Barge-In (listen while speaking)

Syncli sessions are full-duplex: the bot keeps listening while it speaks. Users can interrupt mid-sentence (barge-in). Your frontend must send microphone audio for the entire call β€” the server runs STT on uplink even during TTS.

Required frontend changes

// βœ… Load script & get mobile-optimized constraints
// <script src="https://apidev.syncli.cloud/dev/js/voice-audio.js"></script>
const voiceConstraints = SyncliVoiceAudio.getVoiceAudioConstraints();
localStream = await navigator.mediaDevices.getUserMedia(voiceConstraints);

// βœ… Set up AEC warmup notifier to prevent false barge-in during greeting
const aecWarmup = SyncliVoiceAudio.createAecWarmupNotifier(dc);
dc.onopen = () => { if (aecWarmup) aecWarmup.onDataChannelOpen(); };
// Trigger on remote audio play:
audioEl.play().then(() => { if (aecWarmup) aecWarmup.onRemoteAudioPlaying(); });
      

Barge-in is triggered server-side when a finalized transcript (β‰₯ ~12 characters) arrives while the bot is speaking. No special client β€œinterrupt” message is required.

πŸ’¬ Send Text While in a Voice Call

You can send text messages to the bot during a WebRTC voice session. The bot receives the text as if you had spoken it, and responds via the same audio stream.

// Send text through the data channel
dc.send(JSON.stringify({ type: 'chat', text: 'Your message' }))
      

What happens on the backend:

  1. The injected text is treated identically to spoken audio input
  2. The LLM processes it immediately (same pipeline)
  3. TTS generates audio for the bot response
  4. The response plays through the WebRTC audio track

Message flow (data channel β†’ backend):

// β†’ FROM frontend (you send this)
{ type: 'chat', text: 'What is the weather?' }

// ← TO frontend (backend event types)
{ type: 'user-transcript',     data: { text } }          // STT transcript
{ type: 'bot-output',           data: { text } }          // TTS-expanded β€” do NOT show in chat UI
{ type: 'bot-output-raw',      data: { text } }          // Canonical text β€” show this in chat UI
{ type: 'bot-started-speaking' }                          // Do NOT mute mic here
{ type: 'bot-stopped-speaking' }
      

πŸ’‘ Tip: Use type: 'chat' to build hybrid UIs where users can both speak and type during voice sessions. The 'user-transcript' event will arrive for both spoken and typed input (deduplicated in the code above).

πŸ”Œ Plugin System (Dynamic Tools + Persona)

Every API key can have custom plugins injected into the voice agent. A plugin is a piece of Python code + a persona addon that gets loaded at session start. The agent can then call your custom tools and speak with your persona.

1. Create a plugin via API:

POST /api/plugins/{api_key}
{
  "name": "scheduler",
  "description": "My custom scheduler plugin",
  "persona_addon": "When scheduling, always confirm before creating.",
  "source_code": "PLUGIN = Plugin(name='scheduler', persona_addon='', tools=[\n  Tool(name='check_schedule', description='...', parameters={...}, handler=my_handler)\n])",
  "tools": [
    {
      "name": "check_schedule",
      "description": "Check if a time slot is free",
      "parameters": { "type": "object", "properties": { "date": { "type": "string" } } },
      "required": ["date"]
    }
  ]
}
      

2. Plugin Python Source (what goes in source_code):

from plugins.types import Plugin, Tool

async def check_schedule(params, deps):
    # params.tool_call_id  β€” unique ID from the LLM
    # params.arguments       β€” dict of parsed arguments
    # deps["user_id"]       β€” current user
    # deps["google_actions"] β€” Google calendar / gmail client
    # deps["chat_id"]       β€” current chat context
    args = params.arguments
    return {"free_slots": ["10:00 AM", "2:00 PM"]}

PLUGIN = Plugin(
    name="scheduler",
    persona_addon="You are a helpful scheduling assistant.",
    tools=[
        Tool(
            name="check_schedule",
            description="Check availability for a date",
            parameters={"type":"object","properties":{"date":{"type":"string"}}},
            required=["date"],
            handler=check_schedule
        )
    ]
)
      

3. Plugin lifecycle endpoints:

POST   /api/plugins/{api_key}                  # Create
GET    /api/plugins/{api_key}                  # List
GET    /api/plugins/{api_key}/{plugin_id}        # Get one
PATCH  /api/plugins/{api_key}/{plugin_id}       # Update source_code
DELETE /api/plugins/{api_key}/{plugin_id}        # Delete
POST   /api/plugins/{api_key}/{plugin_id}/toggle{"enabled":false}
POST   /api/plugins/{api_key}/test               # Dry-run without saving
      

πŸ’‘ Tip: The persona_addon is injected into the system prompt at session start, shaping how the agent speaks. Tools are registered with the LLM so it can call them automatically when the user asks for help.

🧩 Agent Session Config (per-call behaviour)

Pass an optional agent_config object on POST /start to shape how the voice agent behaves for that session β€” without changing portal defaults for other calls. Omitted agent_config keeps today's behaviour (portal persona, knowledge base, API tool set).

Inline config on POST /start

{
  "end_user_session_id": "bs_abc123",
  "client_timezone": "America/New_York",
  "client_timestamp": "2026-06-19T12:00:00.000Z",
  "agent_config": {
    "persona": {
      "name": "Support",
      "identity": "You are Acme Corp support β€” concise and helpful.",
      "rules": "Escalate billing issues with transfer_to_human."
    },
    "persona_addon": "Always greet by first name when known.",
    "knowledge_base_id": "kb_xxxxxxxx",
    "tools": {
      "mode": "allowlist",
      "allow": ["save_visitor_info", "transfer_to_human"],
      "include_plugins": true,
      "include_mcp": true
    },
    "mcp_servers": [
      {
        "server_id": "crm",
        "url": "http://localhost:3100/sse",
        "headers": { "Authorization": "Bearer YOUR_MCP_TOKEN" }
      }
    ],
    "plugin_ids": ["scheduler"],
    "llm": { "temperature": 0.15, "max_tokens": 140 },
    "web_search": false,
    "metadata": { "page": "/pricing", "plan": "pro" }
  }
}

Named profile via agent_config_ref

Define reusable profiles in voice_agent/extensions/agent_config_service.py (AGENT_PROFILES), then reference them from your frontend:

{
  "agent_config": {
    "agent_config_ref": "support_voice",
    "metadata": { "locale": "en-US" }
  }
}

Developer service / controller pattern

extensions/agent_config_service.py   # AGENT_PROFILES + resolve_for_session()
extensions/agent_config_controller.py # validation + gateway hook
extensions/agent_config_router.py     # GET /api/agent-config/profiles
                                      # POST /api/agent-config/resolve (dry-run)

Copy extensions/template_service.py for CRM sync, transcript webhooks, etc.

πŸ’‘ Tip: Use POST /api/agent-config/resolve to preview the merged config before starting a voice session. The gateway stores resolved agent_config on the session and forwards it to the WebRTC offer automatically.

πŸ”Œ Webhook CRM / QA Integration

At the end of every WebRTC voice session, the agent automatically executes an AI summarization task and issues a secure HTTP POST request containing detailed conversation analysis, extracted key topics, structured action items, customer sentiment, and chronological transcripts to your configured endpoint.

Webhook POST Payload Structure:

{
  "event": "voice_agent.session_completed",
  "event_version": "2.0",
  "timestamp": 1779556480,
  "session": {
    "session_id": "call_8f3d10ab_cd39_4f0b",
    "started_at": 1779556296,
    "ended_at": 1779556480,
    "duration_seconds": 184,
    "completed_successfully": true,
    "resolution_status": "resolved_by_ai",
    "termination_reason": "user_ended_call",
    "ended_by": "user",
    "was_transferred_to_human": false,
    "handoff_reason": null
  },
  "developer": {
    "developer_user_id": "dev_usr_0a12",
    "workspace_id": "wrk_123",
    "project_id": "proj_456",
    "resolved_tracking_key": "[email protected]"
  },
  "user_profile": {
    "user_id": "usr_99b0c2a1",
    "name": "Obiajulu",
    "email": "[email protected]",
    "phone": null,
    "client_ip": "192.168.1.45",
    "is_returning_user": true
  },
  "client_context": {
    "device_type": "desktop",
    "browser": "chrome",
    "os": "macos",
    "location": {
      "country": "US",
      "region": null,
      "city": null,
      "is_vpn_or_local": true
    },
    "user_agent": "Mozilla/5.0..."
  },
  "analytics": {
    "overall_sentiment": "Positive / Inquisitive",
    "sentiment_score": 0.82,
    "conversation_summary": "The user inquired about Kanban boards and smart attendance templates. Zara explained how to configure boards in the Projects tab and link attendees. The user expressed satisfaction.",
    "key_topics_extracted": [
      "Kanban boards",
      "Smart attendance",
      "Video meetings"
    ],
    "detected_intents": ["product_question", "setup_guidance"],
    "faq_candidates": [
      "How do I configure Kanban boards?",
      "How does smart attendance work?"
    ],
    "action_items": [
      {
        "owner": "user",
        "task": "Configure smart attendance via video meetings",
        "priority": "medium",
        "due_date": null,
        "status": "open"
      }
    ]
  },
  "quality_metrics": {
    "ai_resolution_score": 0.91,
    "customer_satisfaction_estimate": 0.88,
    "conversation_friction_score": 0.12,
    "first_response_time_ms": 900,
    "avg_response_time_ms": 1200,
    "assistant_talk_ratio": 0.58,
    "user_talk_ratio": 0.42,
    "interruption_count": 0,
    "clarification_count": 1,
    "fallback_count": 0
  },
  "transcript": [
    {
      "speaker": "assistant",
      "text": "Good afternoon, OBI! I’m Zara, here to help you navigate Syncli's workspace.",
      "timestamp": 1779556296
    },
    {
      "speaker": "user",
      "text": "How do I configure the Kanban boards and smart attendance?",
      "timestamp": 1779556310
    }
  ],
  "metadata": {
    "agent_id": "agent_zara",
    "agent_name": "Zara",
    "agent_version": "6.1",
    "prompt_version": "2026-05-voice-v3",
    "source": "SyncliVoiceGateway",
    "environment": "production"
  }
}
      

πŸ’‘ Note: If configured, the webhook will contain an Authorization: Bearer <Token> header matching your designated secret. Use the **Rate Capping** panel to set up your URL/token and send live test connection payloads!

Deploy This Portal

Completely self-contained. No build step.

# Python 3 one-liner
python3 -m http.server 8443

# Node.js (npx serve)
npx serve -l 8443

# Vite project
npm create vite@latest my-syncli -- --template vanilla
cp index.html my-syncli/index.html
cd my-syncli && npm install && npm run dev

# S3 / Cloudflare Pages / Vercel / Netlify
# Upload index.html directly. No build step.