CRM-Oktivo App Integration Documentation
Table of Contents
- Overview
- Architecture
- Data Models
- API Endpoints
- File Changes
- Environment Configuration
- UI Testing Guide
- API Testing Guide
- Troubleshooting
Overview
This integration enables CRM users to initiate browser-based calls (WebRTC) to prospects via the Oktivo App voice platform. Key features:
- OAuth Connection: CRM organizations connect to Oktivo App accounts for shared credits
- Browser-to-Phone Calls: WebRTC audio from browser routed via LiveKit/SIP to prospect's phone
- Call Recording: Automatic recording of all calls
- AI Transcription: Speech-to-text with speaker labels (CRM User / Prospect)
- AI Analysis: Sentiment classification, summary, and key points extraction
- Call History: Full history with recordings, transcriptions, and analysis per lead
Architecture
High-Level System Diagram
OAuth Connection Flow
Call Flow
Post-Call Processing Flow
Data Models
OktivoConnection (CRM Database)
Stores OAuth connection for CRM organizations.
typescript
// File: oktivo_crm/backend/src/models/OktivoConnection.ts
interface IOktivoConnection {
connectionId: string; // "oktivo_conn_xxx"
organizationId: ObjectId; // CRM Organization (unique)
oktivoAccountId: string; // Connected Oktivo org ID
oktivoAccountName: string; // Display name
encryptedAccessToken: string; // AES-256-GCM encrypted
encryptedRefreshToken: string; // AES-256-GCM encrypted
tokenExpiresAt: Date; // Token expiration
status: 'connected' | 'disconnected' | 'token_expiring';
scopes: string[]; // Granted OAuth scopes
createdAt: Date;
updatedAt: Date;
}Indexes:
organizationId: 1(unique)connectionId: 1(unique)tokenExpiresAt: 1status: 1
CrmConnection (Oktivo Database)
Stores CRM client registrations.
typescript
// File: oktivo_voice/backend/client_backend/src/models/CrmConnection.ts
interface ICrmConnection {
connectionId: string; // "crm_conn_xxx"
oktivoOrgId: ObjectId; // Oktivo organization
crmOrganizationId: string; // CRM organization ID
crmOrganizationName: string; // Display name
status: 'active' | 'revoked';
grantedScopes: string[];
lastActivityAt: Date;
createdAt: Date;
updatedAt: Date;
}Indexes:
oktivoOrgId: 1, crmOrganizationId: 1(unique)connectionId: 1(unique)status: 1
HumanCall (Oktivo Database)
Records browser-to-phone calls.
typescript
// File: oktivo_voice/backend/client_backend/src/models/HumanCall.ts
interface IHumanCall {
callId: string; // "hcall_xxx"
crmConnectionId: string;
crmOrganizationId: string;
crmUserId: string;
leadPhone: string; // E.164 format
leadName: string;
livekitRoomName: string; // "hcall-room-xxx"
status: 'connecting' | 'ringing' | 'in_progress' | 'ended' | 'failed';
initiatedAt: Date;
answeredAt: Date | null;
endedAt: Date | null;
duration: number | null; // seconds
disconnectReason: string | null;
recordingUrl: string | null;
transcription: {
text: string;
segments: Array<{
speaker: 'crm_user' | 'prospect';
text: string;
startTime: number;
endTime: number;
}>;
processedAt: Date;
} | null;
analysis: {
sentiment: 'positive' | 'neutral' | 'negative';
summary: string; // 2-4 sentences
keyPoints: string[]; // 3-5 items
processedAt: Date;
} | null;
transcriptionStatus: 'pending' | 'processing' | 'completed' | 'failed';
analysisStatus: 'pending' | 'processing' | 'completed' | 'failed';
creditsCost: number | null;
}Indexes:
callId: 1(unique)crmOrganizationId: 1, initiatedAt: -1leadPhone: 1, initiatedAt: -1status: 1transcriptionStatus: 1, analysisStatus: 1
API Endpoints
CRM Backend Routes (/api/oktivo/*)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /auth/url | JWT | Generate OAuth authorization URL |
GET | /auth/callback | Public | Handle OAuth callback |
GET | /connection | JWT | Get connection status |
DELETE | /connection | JWT | Disconnect integration |
GET | /credits | JWT | Get credit balance |
POST | /calls/:leadId | JWT | Initiate call to lead |
POST | /calls/direct | JWT | Direct call (no lead lookup) |
GET | /calls/:callId | JWT | Get call details |
GET | /calls/lead/:leadId/history | JWT | Get lead's call history |
GET | /calls/history | JWT | Get call history by phone |
Oktivo Backend Routes (/api/crm/*)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /oauth/token | Client credentials | Exchange code for tokens |
POST | /oauth/refresh | Client credentials | Refresh access token |
GET | /credits | Bearer token | Get credit balance |
POST | /calls | Bearer token | Initiate human call |
GET | /calls/history | Bearer token | Get paginated call history |
GET | /calls/:callId | Bearer token | Get call details |
POST | /calls/:callId/end | Bearer token | Emergency call termination |
Request/Response Examples
Initiate Call (Oktivo Backend):
http
POST /api/crm/calls
Authorization: Bearer <access_token>
Content-Type: application/json
{
"phone": "+919876543210",
"name": "John Doe",
"crmUserId": "user_123",
"crmOrganizationId": "org_456"
}Response:
json
{
"callId": "hcall_abc123",
"roomName": "hcall-room-abc123",
"roomToken": "eyJ...",
"sipPhoneNumber": "+1234567890"
}Get Call Details:
json
{
"callId": "hcall_abc123",
"status": "ended",
"duration": 145,
"recordingUrl": "https://s3.../recording.mp3",
"transcription": {
"text": "Hello, I'm calling about...",
"segments": [
{ "speaker": "crm_user", "text": "Hello...", "startTime": 0, "endTime": 3.5 },
{ "speaker": "prospect", "text": "Hi...", "startTime": 3.5, "endTime": 7.2 }
]
},
"analysis": {
"sentiment": "positive",
"summary": "Productive call about 3BHK property interest...",
"keyPoints": [
"Interested in 3BHK property",
"Budget around 80 lakhs",
"Prefers south Bangalore"
]
}
}File Changes
CRM Repository (oktivo_crm/)
New Files Created
| File | Description |
|---|---|
backend/src/models/OktivoConnection.ts | MongoDB model for OAuth connections |
backend/src/repositories/OktivoConnectionRepository.ts | CRUD operations |
backend/src/services/OktivoOAuthService.ts | OAuth: auth URL, token exchange, refresh |
backend/src/services/OktivoApiClient.ts | HTTP client with auto token refresh |
backend/src/routes/oktivo.routes.ts | Express routes for /api/oktivo/* |
backend/src/controllers/oktivo.controller.ts | Route handlers |
backend/src/config/oktivo.config.ts | Configuration |
app/src/services/oktivoApi.ts | Frontend API client |
app/src/hooks/useOktivoConnection.ts | Connection state hook |
app/src/hooks/useCallStatus.ts | Call status via LiveKit data channel |
app/src/hooks/useWebRTCReconnect.ts | Auto-reconnect logic |
app/src/components/Settings/OktivoIntegration.tsx | Settings UI component |
app/src/components/CallPanel/CallPanel.tsx | Call UI with controls |
app/src/components/CallPanel/CallControls.tsx | Mute/End buttons |
app/src/components/CallPanel/CallStatus.tsx | Status display |
app/src/components/Lead/CallHistory.tsx | Call history list |
app/src/components/Lead/CallHistoryItem.tsx | Individual call record |
app/src/utils/callErrors.ts | Error message mapping |
app/src/pages/OktivoOAuthCallbackPage.tsx | OAuth callback handler |
Modified Files
| File | Change |
|---|---|
backend/src/app.ts | Added oktivoRoutes at /api/oktivo |
backend/.env.example | Added Oktivo OAuth variables |
app/src/components/LeadDetail.tsx | Added CallHistory component |
app/src/components/IntegrationsTab.tsx | Added OktivoIntegration component |
app/package.json | Added livekit-client: ^2.20.1 |
Oktivo Voice Repository (oktivo_voice/backend/client_backend/)
New Files Created
| File | Description |
|---|---|
src/models/CrmConnection.ts | MongoDB model for CRM connections |
src/models/HumanCall.ts | MongoDB model for call records |
src/repositories/CrmConnectionRepository.ts | CRUD operations |
src/repositories/HumanCallRepository.ts | CRUD + queries |
src/middleware/crmAuth.middleware.ts | Token validation + credit check |
src/services/humanCall.service.ts | Call lifecycle, transcription, analysis |
src/routes/crm.routes.ts | Express routes for /api/crm/* |
src/controllers/crm.controller.ts | Route handlers |
Modified Files
| File | Change |
|---|---|
src/app.ts | Added crmRoutes at /api/crm with rate limiter |
src/controllers/webhook.controller.ts | Route hcall-room-* to humanCallService |
src/config/index.ts | Added CRM OAuth config variables |
src/middleware/rateLimiter.middleware.ts | Added createCrmOAuthRateLimiter() |
env/client-backend.env | Added CRM OAuth variables |
Environment Configuration
CRM Backend (oktivo_crm/backend/.env)
env
# Oktivo App OAuth Configuration
OKTIVO_CLIENT_ID=crm_client_xxx
OKTIVO_CLIENT_SECRET=your_secret_here
OKTIVO_AUTH_URL=https://api.oktivo.ai/oauth/authorize
OKTIVO_TOKEN_URL=https://api.oktivo.ai/api/crm/oauth/token
OKTIVO_API_URL=https://api.oktivo.ai/api/crm
OKTIVO_REDIRECT_URI=https://crm.oktivo.ai/api/oktivo/auth/callback
# Token encryption key (32 bytes hex)
OKTIVO_TOKEN_ENCRYPTION_KEY=your_32_byte_hex_key_hereOktivo Backend (oktivo_voice/backend/client_backend/.env)
env
# CRM OAuth Provider Configuration
CRM_OAUTH_CLIENT_ID=crm_client_xxx
CRM_OAUTH_CLIENT_SECRET=your_client_secret_here
CRM_OAUTH_REDIRECT_URI=https://crm.oktivo.ai/api/oktivo/auth/callback
# NOTE: CRM OAuth tokens use the unified JWT configuration:
# - ACCESS_TOKEN_SECRET / ACCESS_TOKEN_EXPIRY for access tokens
# - REFRESH_TOKEN_SECRET / REFRESH_TOKEN_EXPIRY for refresh tokensUI Testing Guide
Prerequisites
- [ ] CRM Backend running:
npm run devinoktivo_crm/backend - [ ] CRM Frontend running:
npm run devinoktivo_crm/app - [ ] Oktivo Backend running:
npm run devinoktivo_voice/backend/client_backend - [ ] Valid Oktivo App account with credits
- [ ] Environment variables configured
- [ ] Logged in as Organization Admin in CRM
Test 1: Connect Integration
Navigate to Settings
- Click Settings in sidebar
- Click Integrations tab
Find Oktivo Section
- Look for "Oktivo App" card
- Should show "Connect" button
Initiate OAuth
- Click "Connect"
- Redirected to Oktivo App login
Authorize
- Login to Oktivo App (if needed)
- Click "Authorize"
Verify Connection
- Redirected back to CRM
- Should see:
- ✅ "Connected" status
- ✅ Account name displayed
- ✅ Credit balance shown
- ✅ "Disconnect" button visible
Test 2: Make a Call
Find a Lead
- Go to Leads page
- Select a lead with a phone number
Initiate Call
- Click "📞 Call Now" button
- Confirmation dialog appears
Grant Microphone
- Browser prompts for permission
- Click "Allow"
Confirm Call
- Click "Yes, Call Now"
Monitor Status
- Watch status change:
Connecting...→Ringing...→In Progress
- Timer starts counting
- Watch status change:
Test Controls
- Click Mute → verify mic muted
- Click Unmute → verify restored
- Click End Call → call terminates
Test 3: View Call History
Wait for Processing
- After call ends, wait 30-60 seconds
View History
- Stay on lead detail page
- Scroll to "Oktivo App Calls" section
Verify Record
- Call appears with timestamp, duration, status
Expand Details
- Click to expand
- Verify:
- 🎵 Recording player
- 📝 Transcription text
- 🤖 AI Analysis (sentiment, summary, key points)
Test 4: Disconnect Integration
Go to Settings
- Settings → Integrations
Disconnect
- Click "Disconnect"
- Confirm in dialog
Verify
- Shows "Connect" button again
- No account info displayed
Test 5: Error Scenarios
| Scenario | Steps | Expected Result |
|---|---|---|
| No phone | Try to call lead without phone | Button disabled, tooltip shows "No phone number" |
| No credits | Use account with 0 credits | Error: "Insufficient credits..." |
| Not connected | Disconnect, try to call | Error: "Please connect to Oktivo App..." |
| Mic denied | Deny microphone permission | Error about microphone access |
| Concurrent call | Start 2 calls at once | Second call blocked: "Active call exists" |
API Testing Guide
Test OAuth Flow
bash
# 1. Exchange code for tokens
curl -X POST http://localhost:3000/api/crm/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "auth_code_here",
"redirect_uri": "https://crm.example.com/callback",
"client_id": "crm_client_xxx",
"client_secret": "your_secret"
}'
# Response: { "access_token": "...", "refresh_token": "...", "expires_in": 3600 }Test Credit Balance
bash
TOKEN="your_access_token"
curl -X GET http://localhost:3000/api/crm/credits \
-H "Authorization: Bearer $TOKEN"
# Response: { "balance": 100, "currency": "credits" }Test Call Initiation
bash
curl -X POST http://localhost:3000/api/crm/calls \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phone": "+919876543210",
"name": "Test Lead",
"crmUserId": "user_123",
"crmOrganizationId": "org_456"
}'
# Response: { "callId": "hcall_xxx", "roomName": "...", "roomToken": "..." }Test Call History
bash
curl -X GET "http://localhost:3000/api/crm/calls/history?phone=%2B919876543210" \
-H "Authorization: Bearer $TOKEN"
# Response: { "calls": [...], "total": 5, "page": 1, "pageSize": 20 }Test Call Details
bash
curl -X GET http://localhost:3000/api/crm/calls/hcall_xxx \
-H "Authorization: Bearer $TOKEN"
# Response: Full call record with transcription and analysisTroubleshooting
Connection Issues
| Problem | Cause | Solution |
|---|---|---|
| OAuth redirect fails | Wrong redirect URI | Check OKTIVO_REDIRECT_URI matches exactly |
| "Invalid client" error | Wrong credentials | Verify OKTIVO_CLIENT_ID and OKTIVO_CLIENT_SECRET |
| Token refresh fails | Expired refresh token | User must reconnect in Settings |
| CORS error | Backend misconfigured | Check CORS origins in both backends |
Call Issues
| Problem | Cause | Solution |
|---|---|---|
| Call button disabled | No phone / Not connected / Active call | Check lead has phone, verify connection, end other calls |
| "Insufficient credits" | Account has 0 credits | Add credits in Oktivo App |
| No audio | Mic not working | Check browser permissions, try different browser |
| Call stuck on "Connecting" | LiveKit unreachable | Check network, verify LiveKit server |
| WebRTC failed | Network/firewall issue | Try different network, check firewall rules |
Post-Call Processing
| Problem | Cause | Solution |
|---|---|---|
| No transcription | Processing delay / API failure | Wait 1-2 min, check Deepgram API key |
| No analysis | Transcription not ready | Wait for transcription to complete first |
| No recording | Call too short (<10s) | Make longer test call |
Error Codes
| HTTP | Error | Meaning |
|---|---|---|
| 401 | invalid_token | Token expired or invalid |
| 402 | insufficient_credits | Organization has 0 credits |
| 403 | connection_required | No active CRM connection |
| 404 | call_not_found | Call ID doesn't exist |
| 429 | rate_limit_exceeded | Too many requests (30/min) |
Quick Reference
Key Files by Feature
| Feature | CRM Files | Oktivo Files |
|---|---|---|
| OAuth | OktivoOAuthService.ts, oktivo.routes.ts | crm.routes.ts, crm.controller.ts |
| Calls | oktivoApi.ts, CallPanel.tsx | humanCall.service.ts, crm.controller.ts |
| History | CallHistory.tsx, CallHistoryItem.tsx | HumanCallRepository.ts |
| Settings | OktivoIntegration.tsx | N/A |
LiveKit Integration
- Room prefix:
hcall-room-* - Data channel: Used for status updates
- Webhook events:
room.started,participant.joined,participant.left,room.ended
Rate Limits
- CRM endpoints: 30 requests per 60-second window
- Key: Connection ID (authenticated) or IP (OAuth)
- Rate limit exceeded: Returns 429 with
Retry-Afterheader