DEV Community

Jason Shouldice
Jason Shouldice

Posted on • Edited on • Originally published at vicistack.com

Stop Alt-Tabbing: How to Wire VICIdial Into Your CRM Properly

Every time an agent switches between VICIdial and the CRM, you lose 3-5 seconds. At 40-60 switches per hour across a 50-seat floor running 8 hours, that's 25-40 hours of lost productivity per day. Not because agents are slow — because the systems don't talk to each other.

VICIdial has the integration layer to fix this. Most operators just never set it up. The APIs exist. The hook points exist. The agent screen supports embedded CRM views. This guide covers the four integration patterns that actually work, the two APIs that power them, and the middleware that glues everything together.

VICIdial Is Not a CRM

This is the first mistake to correct. VICIdial is a dialer with a lead database. It stores names, phone numbers, addresses, statuses, and call history. It does not store deal pipelines, account hierarchies, contract terms, or support tickets. Trying to force vicidial_list into doing CRM work leads to bloated custom fields, broken reporting, and agents who hate their tools.

The right architecture: VICIdial handles dialing, call routing, recording, and agent management. Your CRM handles relationship data, deal tracking, and workflow automation. The integration layer keeps both synchronized so agents see the full picture without leaving the dialer and your CRM reflects every call outcome without manual data entry.

The Four Integration Patterns

After building CRM integrations for VICIdial shops of all sizes, virtually every use case maps to one of four patterns. Most deployments need two or three running together.

Pattern 1: Screen Pops

When a call connects, the agent sees the CRM record inside VICIdial's agent interface. No window switching. VICIdial's Web Form feature loads a URL in an embedded IFRAME and substitutes lead data into the URL dynamically:

https://your-crm.example.com/contact/view?phone=--A--phone_number--B--&email=--A--email--B--&lead_id=--A--vendor_lead_code--B--
Enter fullscreen mode Exit fullscreen mode

The --A--fieldname--B-- variables get replaced with actual values from the lead record at call time. Your CRM receives these as query parameters and loads the matching record.

For Salesforce, you can point the Web Form URL directly at a Lightning record page, though some orgs block IFRAME embedding via X-Frame-Options headers. If that's the case, build a lightweight Visualforce page that queries Salesforce data and renders a clean screen pop optimized for the IFRAME — no Salesforce chrome, just the data agents need.

Pattern 2: Disposition Sync

When an agent dispositions a call in VICIdial, the outcome needs to reach the CRM automatically. A "SALE" disposition should update the deal stage to "Closed Won." A "CALLBK" should create a follow-up task. A "DNC" should flag the contact.

VICIdial fires HTTP requests via the Dispo Call URL and Start Call URL when specific events occur — call connect, call end, disposition entry. You configure them in Campaign Detail:

https://your-middleware.example.com/webhook/call_end?lead_id=--A--lead_id--B--&dispo=--A--dispo--B--&talk_time=--A--talk_time--B--&agent=--A--user--B--&vendor_code=--A--vendor_lead_code--B--&recording=--A--recording_filename--B--
Enter fullscreen mode Exit fullscreen mode

Your middleware catches the webhook, maps the VICIdial disposition code to a CRM status, and pushes the update via the CRM's API. SALE becomes Closed Won. NI becomes Closed Lost. XFER becomes Negotiation.

The critical detail most people miss: when agents are responsible for manually updating the CRM after every call, data accuracy drops to 60-70% within the first week. Automated disposition sync eliminates human error entirely.

Pattern 3: Lead Injection

New leads created in your CRM need to reach VICIdial's dialing lists automatically. When a marketing form submission creates a HubSpot contact or a Salesforce lead, that record should appear in VICIdial's hopper within minutes — not hours, not after a manual CSV upload.

VICIdial's Non-Agent API has an add_lead function that accepts lead data via HTTP POST:

curl -X POST "https://your-vicidial-server/vicidial/non_agent_api.php" \
  -d "source=CRM_INTEGRATION" \
  -d "user=apiuser" \
  -d "pass=apipass" \
  -d "function=add_lead" \
  -d "phone_number=3125551234" \
  -d "first_name=Sarah" \
  -d "last_name=Johnson" \
  -d "vendor_lead_code=SF-00451298" \
  -d "list_id=1001" \
  -d "phone_code=1"
Enter fullscreen mode Exit fullscreen mode

The vendor_lead_code field is your bridge between systems. Store the CRM record ID there. Every subsequent API call can reference it to match VICIdial leads to CRM records.

Pattern 4: Click-to-Call

An agent working in the CRM clicks a phone number and VICIdial originates the call. No copy-pasting numbers. The Agent API's external_dial function handles this:

curl "https://your-vicidial-server/agc/api.php?\
source=CRM_INTEGRATION&\
user=apiuser&\
pass=apipass&\
agent_user=agent101&\
function=external_dial&\
value=3125551234&\
phone_code=1&\
search=YES&\
preview=NO&\
focus=YES"
Enter fullscreen mode Exit fullscreen mode

The search=YES parameter tells VICIdial to search its lead database for a matching record and load it into the agent's screen. If no match is found, VICIdial creates a new lead record automatically.

The Two APIs That Power Everything

Every CRM integration with VICIdial is built on two APIs. Understanding what each one does — and what it cannot do — prevents wasting time on impossible approaches.

Non-Agent API

Endpoint: https://your-vicidial-server/vicidial/non_agent_api.php

This API handles system-level operations that don't require an active agent session. It's the workhorse for backend integrations, cron jobs, and CRM workflow automation.

Function Purpose CRM Use Case
add_lead Insert a new lead into a list Lead injection from CRM
update_lead Modify an existing lead's data Sync CRM field changes back to VICIdial
lead_field_info Retrieve lead data by lead_id Pull VICIdial data into CRM
lead_search Search leads by phone, vendor code Look up VICIdial records from CRM
add_list Create a new dialing list Automated list management
recording_lookup Find call recordings Link recordings to CRM records
call_log_data Pull call log entries Sync call history to CRM
lead_callback_info Get callback data for a lead Sync callbacks to CRM tasks

Search for a lead by vendor code (the CRM record ID you stored):

curl "https://your-vicidial-server/vicidial/non_agent_api.php?\
source=CRM_INTEGRATION&\
user=apiuser&\
pass=apipass&\
function=lead_search&\
vendor_lead_code=SF-00451298&\
search_method=VENDOR_LEAD_CODE"
Enter fullscreen mode Exit fullscreen mode

Update a lead's data from CRM changes:

curl -X POST "https://your-vicidial-server/vicidial/non_agent_api.php" \
  -d "source=CRM_INTEGRATION" \
  -d "user=apiuser" \
  -d "pass=apipass" \
  -d "function=update_lead" \
  -d "lead_id=485721" \
  -d "address1=123 New Address" \
  -d "city=Springfield" \
  -d "state=IL"
Enter fullscreen mode Exit fullscreen mode

Agent API

Endpoint: https://your-vicidial-server/agc/api.php

Controls actions within a live agent session — dialing, transferring, dispositioning, pausing. Requires an active agent login.

Function Purpose CRM Use Case
external_dial Dial a number from agent's session Click-to-call from CRM
external_pause Pause/unpause the agent CRM-driven agent state control
external_status Disposition the current call Sync CRM outcome back to VICIdial
external_hangup Hang up current call CRM-triggered hangup
external_transfer Transfer the call CRM-initiated transfers
update_fields Update lead fields during live call Push CRM data into VICIdial mid-call
recording Start/stop call recording CRM-triggered recording

Update lead fields during a live call (agent gets fresh data without leaving VICIdial):

curl "https://your-vicidial-server/agc/api.php?\
source=CRM_INTEGRATION&\
user=apiuser&\
pass=apipass&\
agent_user=agent101&\
function=update_fields&\
vendor_lead_code=SF-00451298&\
comments=Qualified - interested in premium plan"
Enter fullscreen mode Exit fullscreen mode

Authentication

Both APIs require user and pass parameters for a VICIdial API user (created in Admin > Users with API access enabled), plus a source identifier that shows up in VICIdial logs for troubleshooting.

Building the Middleware Layer

The Dispo Call URL and Start Call URL point to your middleware — a lightweight web service that receives VICIdial webhooks and translates them into CRM API calls. A Flask app with 50 lines of Python handles the core logic:

  1. Receive the webhook with lead_id, dispo, vendor_code, agent, talk_time, and recording filename
  2. Look up the disposition mapping (SALE → Closed Won, DNC → Closed Lost, etc.)
  3. Call the CRM's API to update the record identified by vendor_code
  4. Log the sync for debugging

The middleware runs on a server accessible from your VICIdial cluster. For HubSpot, you're hitting their CRM API to update deal stages and create engagement records. For Salesforce, you're using the REST API with OAuth tokens to update Lead or Opportunity records. For custom CRMs, you're hitting whatever API endpoint they expose.

Error handling matters here. If the CRM API is down, queue the updates and retry. If the vendor_code doesn't match a CRM record, log it and alert someone. A silent sync failure is worse than no sync at all because people trust the integration and stop checking.

Disposition Sync: The Dispo Call URL Deep Dive

The most common integration requirement is disposition sync. When an agent dispositions a call, update the CRM automatically.

VICIdial provides two URL-based hooks for this, configured in Campaign Detail or In-Group settings.

Start Call URL

Fires an HTTP request when a call connects to an agent. Use cases: log call start time in CRM, create a CRM activity record, trigger screen pop via API, notify external systems that a call is in progress.

https://your-middleware.example.com/webhook/call_start?lead_id=--A--lead_id--B--&phone=--A--phone_number--B--&agent=--A--user--B--&campaign=--A--campaign--B--&uniqueid=--A--uniqueid--B--&vendor_code=--A--vendor_lead_code--B--
Enter fullscreen mode Exit fullscreen mode

VICIdial makes a GET request when the call connects. Your middleware receives the parameters and takes action.

Dispo Call URL

Fires when the agent dispositions the call. This is the primary mechanism for syncing outcomes to your CRM.

https://your-middleware.example.com/webhook/call_end?lead_id=--A--lead_id--B--&dispo=--A--dispo--B--&talk_time=--A--talk_time--B--&agent=--A--user--B--&phone=--A--phone_number--B--&vendor_code=--A--vendor_lead_code--B--&recording=--A--recording_filename--B--&uniqueid=--A--uniqueid--B--&first_name=--A--first_name--B--&last_name=--A--last_name--B--
Enter fullscreen mode Exit fullscreen mode

The --A--dispo--B-- variable contains the disposition status code (SALE, NI, CALLBK, DNC, etc.). Your middleware maps these to CRM outcomes. You can also include --A--recording_filename--B-- to attach recording links to CRM records — valuable for quality assurance and dispute resolution.

Building the Middleware: A Production Example

The Dispo Call URL and Start Call URL point to your middleware — a lightweight web service that receives VICIdial webhooks and translates them into CRM API calls. Here's a production-ready Python example using Flask:

from flask import Flask, request
import requests
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

DISPO_MAP = {
    'SALE':   {'sf_stage': 'Closed Won',  'hubspot_stage': 'closedwon'},
    'NI':     {'sf_stage': 'Closed Lost', 'hubspot_stage': 'closedlost'},
    'CALLBK': {'sf_stage': 'Open',        'hubspot_stage': 'appointmentscheduled'},
    'DNC':    {'sf_stage': 'Closed Lost', 'hubspot_stage': 'closedlost'},
    'NQ':     {'sf_stage': 'Closed Lost', 'hubspot_stage': 'closedlost'},
    'XFER':   {'sf_stage': 'Negotiation', 'hubspot_stage': 'qualifiedtobuy'},
    'DEAD':   {'sf_stage': 'Closed Lost', 'hubspot_stage': 'closedlost'},
}

@app.route('/webhook/call_end', methods=['GET'])
def handle_disposition():
    lead_id = request.args.get('lead_id')
    dispo = request.args.get('dispo')
    vendor_code = request.args.get('vendor_code')
    agent = request.args.get('agent')
    talk_time = request.args.get('talk_time')
    recording = request.args.get('recording')

    logging.info(f"Disposition received: lead={lead_id}, dispo={dispo}, "
                 f"agent={agent}, talk_time={talk_time}")

    if not vendor_code or not dispo:
        return 'MISSING_DATA', 400

    mapping = DISPO_MAP.get(dispo)
    if not mapping:
        logging.warning(f"Unmapped disposition: {dispo}")
        return 'UNMAPPED_DISPO', 200

    update_crm(vendor_code, dispo, mapping, agent, talk_time, recording)
    return 'OK', 200
Enter fullscreen mode Exit fullscreen mode

This middleware runs on a server accessible from your VICIdial cluster. The vendor_lead_code field ties the VICIdial lead to the CRM record. Error handling matters here: if the CRM API is down, queue the updates and retry. If the vendor_code doesn't match a CRM record, log it and alert someone. A silent sync failure is worse than no sync at all because people trust the integration and stop manually checking.

Salesforce-Specific Implementation

Salesforce is the most common CRM paired with VICIdial in enterprise environments. The integration touches all four patterns.

Screen Pops in Salesforce

Simple approach: Set your campaign's Web Form URL to a Salesforce record page:

https://yourorg.lightning.force.com/lightning/r/Lead/--A--vendor_lead_code--B--/view
Enter fullscreen mode Exit fullscreen mode

The vendor_lead_code must contain the Salesforce Lead ID (e.g., 00Q5e000009abc).

Problem: Salesforce Lightning in an IFRAME has mixed results. Some orgs block embedding via X-Frame-Options headers. If blocked, you have three options: use Salesforce Classic URLs instead, adjust Session Settings to allow framing (Setup > Session Settings > uncheck clickjack protection), or build a Visualforce page.

Recommended approach: Build a lightweight Visualforce page that queries Salesforce data and renders a clean screen pop optimized for the agent screen IFRAME. No Salesforce chrome — just the data agents need, loading in under a second. Include previous interactions, deal stage, account value, and notes.

Disposition Sync to Salesforce

Your middleware needs an OAuth connected app with appropriate scopes. Store the refresh token securely. Use the Salesforce REST API to update Lead or Opportunity records based on the disposition mapping.

Watch API limits — a busy 50-seat floor generating hundreds of dispositions per hour can eat into daily API allocations. If you're hitting limits, batch updates or use the Bulk API for end-of-day reconciliation.

HubSpot-Specific Implementation

HubSpot's API is more forgiving for this use case. Private app tokens work well for server-to-server integration.

Lead Injection from HubSpot

HubSpot Workflows can trigger the lead injection side. When a contact enters a specific lifecycle stage or list membership, fire a webhook to your middleware, which calls VICIdial's add_lead endpoint. The HubSpot contact ID goes into vendor_lead_code for bidirectional linking.

Call Activity Logging

Use HubSpot's engagements API to log call activities with duration, recording links (point them at your VICIdial recording URL), and disposition outcomes. This gives your HubSpot users a complete call history on every contact without any manual entry.

Disposition Sync

Map VICIdial dispositions to HubSpot deal stages or lifecycle stages. SALE moves the deal to "Closed Won." CALLBK creates a task with the callback date and time. DNC sets the contact's marketing status to "Do Not Contact."

Custom CRM Integration

If you're running a custom or niche CRM, the pattern is the same — the only difference is which API endpoints your middleware hits. The VICIdial side (Dispo Call URL, Start Call URL, Non-Agent API, Agent API) works identically regardless of what CRM you're connecting to.

For CRMs without a proper API, you can fall back to database-level integration: your middleware writes directly to the CRM's database tables. This is fragile and bypasses business logic, but for legacy systems without APIs it's sometimes the only option. Document it heavily because the next person maintaining it will need the context.

Zapier and No-Code Integration

If you don't have development resources for a custom middleware, Zapier and similar automation platforms can bridge VICIdial and your CRM. The pattern: set your Dispo Call URL to a Zapier webhook, map the incoming VICIdial parameters to CRM fields in Zapier's interface, and let Zapier handle the CRM API calls.

This works for low-to-medium volume operations (under 1,000 dispositions per day). At higher volumes, Zapier's task limits and processing delays become problems — a dedicated middleware is more reliable. But for a 10-seat shop that needs basic dispo sync to HubSpot without hiring a developer, Zapier is a legitimate starting point.

Recording Links in CRM

One of the most valuable integration points that operators overlook: attaching call recording links to CRM records. Include --A--recording_filename--B-- in your Dispo Call URL, and your middleware can construct a playback URL pointing to VICIdial's recording archive.

This gives your quality assurance team, sales managers, and dispute resolution staff direct access to call recordings from within the CRM. No more searching through VICIdial's recording interface to find the right file — click the link on the CRM record and listen.

API Security and Best Practices

Both VICIdial APIs accept credentials as query parameters or POST data. In production, this creates security exposure you need to mitigate:

  1. Always use HTTPS. API credentials in plaintext over HTTP is a non-starter. Set up SSL certificates on your VICIdial web server if you haven't already.
  2. Create dedicated API users with minimal permissions. Don't reuse admin or agent credentials for API access. Create a user with only the API functions enabled that your integration actually needs.
  3. Restrict API access by IP in Admin > System Settings > API settings. Whitelist only the IPs of your CRM servers and middleware. If your middleware runs on AWS or similar cloud, use a static IP or NAT gateway so the whitelist doesn't need constant updating.
  4. Use the source parameter consistently across all API calls. It shows up in VICIdial logs and makes troubleshooting integration issues dramatically easier. Use descriptive names like "SALESFORCE_SYNC" or "HUBSPOT_LEADS" rather than generic identifiers.
  5. Rate limit bulk operations. VICIdial's API isn't designed for thousands of concurrent requests. For loading 10,000 leads, batch your requests with 50-100ms delays between them. For disposition sync during peak hours (hundreds of concurrent agents), your middleware should queue and serialize CRM API calls to avoid overwhelming either system.
  6. Log everything on both sides. Your middleware should log every incoming webhook from VICIdial and every outgoing API call to the CRM, with timestamps and response codes. When an integration breaks at 5 PM on a Friday, these logs are the difference between a 10-minute diagnosis and a 3-hour debugging session.

Salesforce-Specific Notes

Salesforce Lightning in an IFRAME has mixed results. If your org blocks framing, you have three options: use Salesforce Classic URLs instead, adjust Session Settings to allow framing, or build a Visualforce/LWC screen pop page (recommended — you control exactly what agents see and it loads faster).

For disposition sync to Salesforce, your middleware needs an OAuth connected app with appropriate scopes. Store the refresh token securely. The Salesforce REST API handles Lead and Opportunity updates well, but watch API limits — a busy 50-seat floor generating hundreds of dispositions per hour can eat into daily API allocations.

HubSpot-Specific Notes

HubSpot's API is more forgiving than Salesforce for this use case. Private app tokens work well for server-to-server integration. Use the CRM contacts API for lead lookups and the engagements API to log call activities with duration, recording links, and disposition outcomes.

HubSpot Workflows can trigger the lead injection side — when a contact enters a specific lifecycle stage or list, fire a webhook to your middleware that calls VICIdial's add_lead endpoint.

Testing and Monitoring Your Integration

A CRM integration that works on launch day and silently breaks two weeks later is worse than no integration at all, because everyone trusts the data.

Pre-Launch Testing Checklist

  1. Test every disposition code: Run through each disposition in your DISPO_MAP and verify the CRM record updates correctly. Don't just test SALE — test NI, CALLBK, DNC, DEAD, and every custom status your campaigns use.
  2. Test screen pops with edge cases: What happens when vendor_lead_code is empty? When the CRM record doesn't exist? When the IFRAME URL times out? Your agents will hit these cases daily.
  3. Test lead injection with duplicates: What happens when the CRM tries to inject a lead that's already in VICIdial? The add_lead endpoint should handle this gracefully. If your lists enforce unique phone numbers, duplicate injection will fail silently unless you handle the error response.
  4. Test click-to-call with inactive agents: What happens when the CRM triggers external_dial for an agent who's paused or not logged in? The API returns an error — make sure your CRM handles it with a user-friendly message, not a crash.

Ongoing Monitoring

Set up daily monitoring for:

  • Sync failure count: How many webhook calls failed? Log every HTTP response from the CRM API. If failures spike, investigate immediately.
  • Unmapped dispositions: How many calls used disposition codes that aren't in your DISPO_MAP? These fall through without updating the CRM. Add them to the map or investigate why agents are using unexpected statuses.
  • Latency: How long does the CRM API take to respond? If Salesforce is taking 3+ seconds per update, your middleware request queue will back up during high-volume periods.
  • Lead injection success rate: What percentage of CRM-triggered lead injections succeed? Failures usually mean list permission issues, duplicate phone numbers, or malformed data.

The Most Common Integration Failures

After building dozens of VICIdial-CRM integrations, the same problems come up:

  1. Middleware server runs out of disk space from log files nobody rotated. Set up logrotate on day one.
  2. CRM API token expires and nobody notices for a week because the middleware logs errors silently instead of alerting. Set up active alerting on the first API failure, not after 100 consecutive ones.
  3. VICIdial changes the vendor_lead_code field during list reload because the import file uses a different column mapping. Suddenly every screen pop and dispo sync fails because the bridge between systems is broken.
  4. Campaign cloning doesn't copy the Dispo Call URL. Someone creates a new campaign by cloning an existing one, but the URL doesn't carry over (or it does carry over pointing to the wrong middleware endpoint for this campaign's CRM mapping).

If you're running VICIdial and your CRM integration is still "agents copy-paste between windows," the APIs and hook points exist to fix it today. The hardest part is usually the first integration — once the middleware is running and the vendor_lead_code mapping is in place, adding new sync points takes hours instead of days. ViciStack builds this middleware for every deployment we manage, mapped to your specific disposition codes and CRM fields.

Originally published at https://vicistack.com/blog/vicidial-crm-integration/

Top comments (0)