Build on Dialora
One script tag for the chat widget, a REST API behind scoped keys, an MCP server for AI clients, signed webhooks with retries, n8n templates and numbers routed to agents. The same endpoints the dashboard uses.
The chat widget
One script tag. The widget renders inside a Shadow DOM so your page styles and its styles never meet, and it talks to the agent you chose for your website.
Source: backend/public/widget.js · backend/routes/widget.js · components/settings/chat-widget-settings.tsx
<script src="https://aicall.vitrano.in/widget.js" data-key="wgt_…" async></script>
<!-- optional: name a routing label for this page (Settings → Chat widget → Routing) -->
<script src="https://aicall.vitrano.in/widget.js" data-key="wgt_…" data-agent="pricing" async></script>- Answers from the agent's knowledge base: retrieved passages when the base is indexed, the whole base when it is not.
- Voice input: the visitor taps the mic, the clip is transcribed on our speech recognition and sent as a message; replies to spoken questions are read aloud by the browser.
- Callback request and lead capture: a name and number become a lead in Leads and, where connected, your CRM.
- Handoff: when the visitor asks for a person the thread lands in the Conversations inbox, where your team takes over live.
- A rating prompt at the end of the chat, and surveys when a survey is attached to the agent.
- Allowed origins, title, greeting, colour, starter buttons and auto-open are set in the dashboard.
Where the key comes from. Settings → Integrations → Chat Widget. The key starts with wgt_ and can be regenerated there at any time.
Routing pages to agents. Rules in Settings → Chat widget → Routing match the page URL to an agent, so a pricing page and a support page can be answered by different agents from one embed code. A page can also name a label with data-agent="<label>", which the rules can match on. Without a rule the widget uses the agent chosen in the widget settings.
The API, behind scoped keys
Create a key in Settings → API keys, choose its scopes, and send it in the X-API-Key header. Every endpoint the dashboard uses answers to it, under the same ownership checks, plan limits and compliance gate.
Source: backend/routes/api-keys.js · backend/utils/api-scopes.js · backend/middleware/auth.js
curl -X POST https://aicall.vitrano.in/api/leads \
-H "X-API-Key: ic_…" \
-H "Content-Type: application/json" \
-d '{"name":"Asha Patel","phone":"+919876543210","tags":["website"]}'
curl -X POST https://aicall.vitrano.in/api/calls/dial \
-H "X-API-Key: ic_…" \
-H "Content-Type: application/json" \
-d '{"to":"+919876543210","agentId":"<agent id>","name":"Asha Patel"}'- GET/api/agentsYour agents, with ids for the other calls
- POST/api/leadsAdd a lead: name, phone, tags, custom fields
- GET/api/leadsSearch leads by name, phone or tag
- POST/api/calls/dialCall a number now with an agent
- GET/api/call-logsCall history, transcripts and analysis
- POST/api/appointmentsBook an appointment
- GET/api/eventsThe event log, with replay
Scopes. A scope is <resource>:read or <resource>:write (write includes read), or * for everything a key may reach. The resource is the router the request hits: /api/leads needs leads:read for GET and leads:write for anything else.
What a key never reaches. Users, team, settings, billing and the keys themselves. Those need a signed-in session.
Housekeeping. Keys start with ic_, are shown once, can be given an expiry, record when they were last used, and can be revoked. Creating or revoking one is itself an event.
An MCP server
Point Claude Desktop, Cursor or your own agent framework at /api/mcp and it lists these tools and calls them. Each tool is a thin adapter onto the REST API, so the key's scopes decide what the client may do.
Source: backend/routes/mcp.js · backend/services/mcp-server.js
POST https://aicall.vitrano.in/api/mcp
X-API-Key: ic_…
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"create_lead","arguments":{"name":"Asha Patel","phone":"+919876543210"}}}{
"mcpServers": {
"dialora": {
"url": "https://aicall.vitrano.in/api/mcp",
"headers": { "X-API-Key": "ic_…" }
}
}
}Transport. Streamable HTTP, stateless: POST one JSON-RPC 2.0 message or a batch and the answer comes back as JSON. There is no server-to-client event stream (GET answers 405) and no session; every request stands on its own, authenticated with X-API-Key or a dashboard session.
Tools.
- list_agents
- create_lead
- search_leads
- dial_call
- list_calls
- get_call
- book_appointment
- list_appointments
- campaign_status
- start_campaign
- list_events
- agent_versions
Signed webhooks, with retries
Your workspace webhook is set in Settings → Webhooks, and up to ten extra endpoints can each have their own HTTPS URL, secret, event list and envelope. Every delivery is recorded and can be inspected and replayed from the dashboard.
Source: backend/services/webhook-delivery.js · backend/models/WebhookEndpoint.js · backend/models/WebhookDelivery.js · backend/utils/event-catalog.js
// Node: verify X-Dialora-Signature-V2 on the raw body
const crypto = require("crypto");
const [t, v1] = req.get("X-Dialora-Signature-V2").split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));Headers on every delivery. X-Dialora-Event, X-Dialora-Event-Id, X-Dialora-Attempt, X-Dialora-Timestamp, X-Dialora-Signature (HMAC-SHA256 of the body) and X-Dialora-Signature-V2 (t=<unix time>,v1=<HMAC of "t.body">), which also covers the send time so a captured delivery cannot be replayed later.
Retries. A failed delivery is tried again after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, seven attempts in all, with jitter. A 410 from your server stops retries. An extra endpoint that fails ten deliveries in a row is switched off with the reason kept, and can be switched back on.
Envelopes. dialora (the default) or cloudevents, whose type is in.dialora.<dotted type>.
| Event | When |
|---|---|
| inboundCall | An inbound call reached an agent |
| outboundCall | An outbound call was placed |
| callCompleted | A call ended, with its outcome |
| voicemailLeft | An answering machine picked up and a message was left |
| callBlocked | The compliance gate refused a dial |
| callbackRequested | A caller asked to be called back |
| leadCreated | A lead was added |
| leadQualified | Call analysis rated a lead as qualified |
| leadDispositioned | An agent set a disposition on a lead |
| optOut | A number asked not to be called again |
| consentRecorded | Consent to be contacted was recorded |
| consentRevoked | Consent was withdrawn |
| campaignCompleted | A campaign finished dialling |
| appointmentBooked | An appointment was booked |
| appointmentCanceled | An appointment was cancelled |
| chatHandoff | A chat asked for a human |
| agentTransfer | One agent handed the conversation to another |
| usageThreshold | Usage crossed a plan threshold |
| modelFallback | The local model was unreachable and the hosted fallback answered |
| agentVersionCreated | An agent was changed or restored |
n8n templates
Three ready-made workflows. Import one into n8n and connect the rest to your own tools. The event-driven ones receive their events from Settings → Integrations → n8n, where you paste the workflow's webhook URL; the instant-call one sends your API key in X-API-Key.
Source: frontend/public/n8n-templates/ · backend/services/n8n-service.js · components/settings/n8n-integration.tsx
- Download JSONInstant AI call on form submitA form submission becomes a POST /api/calls/dial with your API key in X-API-Key./n8n-templates/intellicall-instant-call.json
- Download JSONQualified lead to CRM + notifyReceives Dialora events, keeps the lead-qualified ones, and posts the lead to your CRM and a notification webhook./n8n-templates/intellicall-lead-qualified-crm.json
- Download JSONCall log to Google SheetsReceives Dialora events, keeps the call-completed ones, and appends each to a Google Sheet./n8n-templates/intellicall-call-log-to-sheets.json
Numbers, routing and SIP
A phone number belongs to a workspace and points at an agent. What happens when it rings is decided by its routing rules, and the carrier behind it is Twilio or your own SIP trunk.
Source: backend/routes/numbers.js · backend/services/inbound-router.js · components/numbers/inbound-routing-sheet.tsx · backend/routes/sip-trunks.js
- GET/api/numbersNumbers in the workspace, with trunk and agent
- POST/api/numbersAdd a number: provider twilio or sip, agent, hours
- PATCH/api/numbers/:idChange the agent, hours, menu or fallback
- GET/api/sip-trunksYour SIP trunks and their registration status
- POST/api/sip-trunks/:id/testCheck that a trunk's host answers
Inbound routing. Opening hours are checked first, then an optional keypad menu, then the step: connect the agent, forward to a person, take a voicemail, play a message or hang up. Each decision is written into the call log with its reason.
Carriers. Twilio numbers work out of the box; Twilio webhooks are signature-verified. A SIP trunk (Vobiz, Plivo, or any provider) is configured into our Asterisk and can be checked for reachability and with a test call from the dashboard.
Outbound. POST /api/calls/dial places a call with an agent and, optionally, a specific outbound number. The compliance gate (do-not-call list, calling hours, consent) runs before every dial.
Start with a key
Sign up, create a scoped API key in Settings, and the first call is a curl away. Everything on this page is also described for machines in /llms.txt.
