Skip to content

VoiceQA Webhooks

VoiceQA webhooks send call evaluation results to external systems when transcription or analysis is complete. This enables real-time sync with CRMs, analytics platforms, and custom applications.

Different from AI Assistant Webhooks

VoiceQA webhooks send call evaluation data (transcripts, scores, analysis). For chat/messaging webhooks, see AI Assistant Webhooks.


Configuring Webhooks

VoiceQA webhooks are configured at the department level:

  1. Navigate to Voice QA → Departments → [Your Department]
  2. Click Settings or the gear icon
  3. Scroll to the Webhooks section
  4. Add your webhook URLs
  5. Save changes

Webhooks are always active when URLs are configured.


When Webhooks Fire

Webhooks are triggered at two points in the evaluation process:

StageWhenWhat's Included
After TranscriptionWhen transcript is readyinputs, transcript, partial evaluation
After EvaluationWhen full analysis is completeComplete payload with all scores and analysis

Both stages send to all configured webhook URLs.


Webhook Payload

All VoiceQA webhooks send the same payload structure:

json
{
  "voiceQaResult": {
    "inputs": {
      "audio_file_url": "https://storage.fineguide.ai/voiceqa/recording-abc123.mp3",
      "evaluation_id": "eval-550e8400-e29b-41d4-a716-446655440000",
      "type": "voice_qa_evaluation",
      "user_id": "org-abc123"
    },
    "transcript": "Agent: Good morning, thank you for calling Acme Corp. How can I help you today?\nCustomer: Hi, I need help with my recent order.\nAgent: Of course, I'd be happy to help. Can you provide your order number?\nCustomer: Yes, it's 12345.\nAgent: Thank you. I can see your order here...",
    "evaluation": {
      "evaluation_id": "eval-550e8400-e29b-41d4-a716-446655440000",
      "phone": "+373-22-123456",
      "clientName": "John Doe",
      "intent": "Customer called to inquire about order status and request expedited shipping",
      "satisfaction": 8,
      "sentiment": "positive",
      "criterion": [
        {
          "rule_id": "rule-001",
          "rule_name": "Greeting Protocol",
          "category": "general",
          "score": 9,
          "max_score": 10,
          "evidence": "Agent properly greeted with company name: 'Good morning, thank you for calling Acme Corp'",
          "analysis": "The agent followed the standard greeting protocol correctly",
          "suggestion": null
        },
        {
          "rule_id": "rule-002",
          "rule_name": "Problem Identification",
          "category": "customer_service",
          "score": 10,
          "max_score": 10,
          "evidence": "Agent asked clarifying question: 'Can you provide your order number?'",
          "analysis": "Excellent problem identification - agent gathered necessary information efficiently",
          "suggestion": null
        },
        {
          "rule_id": "rule-003",
          "rule_name": "Resolution Confirmation",
          "category": "customer_service",
          "score": 7,
          "max_score": 10,
          "evidence": "Resolution was provided but follow-up was not explicitly confirmed",
          "analysis": "Agent resolved the issue but could improve on confirming customer satisfaction",
          "suggestion": "Always ask 'Is there anything else I can help you with?' before closing"
        }
      ],
      "suggestion": "Overall excellent call. Consider adding explicit resolution confirmation at the end of calls.",
      "conclusion": "Agent demonstrated strong communication skills and efficient problem-solving. Minor improvement needed in call closing procedures."
    },
    "variables": {
      "order_number": "12345",
      "product_mentioned": "Premium Widget",
      "callback_requested": false,
      "upsell_opportunity": true
    }
  }
}

Payload Reference

Top-Level Structure

FieldTypeDescription
voiceQaResultObjectContainer for all evaluation data

Inputs Object

FieldTypeDescription
audio_file_urlStringURL to the original call recording
evaluation_idStringUnique identifier for this evaluation
typeStringAlways "voice_qa_evaluation"
user_idStringOrganization ID that owns this evaluation

Evaluation Object

FieldTypeDescription
evaluation_idStringMatches inputs.evaluation_id
phoneStringCustomer phone number
clientNameStringCustomer name (if identified)
intentStringAI-detected customer intent/purpose of call
satisfactionNumberCustomer satisfaction score (0-10)
sentimentStringOverall sentiment: "positive", "negative", or "neutral"
criterionArrayRule-by-rule evaluation results
suggestionStringOverall improvement recommendations
conclusionStringSummary conclusion of call quality

Criterion Array Items

Each item in the criterion array represents one evaluation rule:

FieldTypeDescription
rule_idStringUnique rule identifier
rule_nameStringHuman-readable rule name
categoryStringRule category (e.g., "general", "sales", "customer_service")
scoreNumberAchieved score for this rule
max_scoreNumberMaximum possible score (depends on rule type)
evidenceStringQuote or observation from the call supporting the score
analysisStringAI explanation of why this score was given
suggestionStringImprovement recommendation (null if score is perfect)

Variables Object

Custom variables extracted during evaluation (configured per-department):

Example VariableTypeDescription
order_numberStringExtracted order/case numbers
product_mentionedStringProducts discussed in call
callback_requestedBooleanWhether customer requested callback
upsell_opportunityBooleanWhether sales opportunity was identified

Integration Examples

Basic Webhook Handler

javascript
const express = require('express');
const app = express();

app.post('/voiceqa-webhook', express.json(), (req, res) => {
  const { voiceQaResult } = req.body;
  
  console.log('Evaluation received:', voiceQaResult.evaluation.evaluation_id);
  console.log('Satisfaction:', voiceQaResult.evaluation.satisfaction);
  console.log('Sentiment:', voiceQaResult.evaluation.sentiment);
  
  // Process the evaluation...
  
  res.sendStatus(200);
});

app.listen(3000);

CRM Contact Update

javascript
app.post('/voiceqa-webhook', async (req, res) => {
  const { voiceQaResult } = req.body;
  const { evaluation, transcript } = voiceQaResult;
  
  // Skip if no phone number
  if (!evaluation.phone) {
    return res.sendStatus(200);
  }
  
  // Find or create contact
  const contact = await crm.findOrCreateContact({
    phone: evaluation.phone,
    name: evaluation.clientName
  });
  
  // Calculate overall score percentage
  const totalScore = evaluation.criterion.reduce((sum, c) => sum + c.score, 0);
  const maxScore = evaluation.criterion.reduce((sum, c) => sum + c.max_score, 0);
  const scorePercent = Math.round((totalScore / maxScore) * 100);
  
  // Add call note
  await crm.addNote(contact.id, {
    type: 'call',
    title: `Call Evaluation - ${scorePercent}%`,
    body: `
**Satisfaction:** ${evaluation.satisfaction}/10
**Sentiment:** ${evaluation.sentiment}
**Intent:** ${evaluation.intent}

**Summary:** ${evaluation.conclusion}

**Suggestions:** ${evaluation.suggestion || 'None'}
    `.trim()
  });
  
  res.sendStatus(200);
});

Analytics Tracking

javascript
app.post('/voiceqa-webhook', async (req, res) => {
  const { voiceQaResult } = req.body;
  const { evaluation, inputs } = voiceQaResult;
  
  // Track overall metrics
  await analytics.track('VoiceQA Call Evaluated', {
    evaluationId: evaluation.evaluation_id,
    satisfaction: evaluation.satisfaction,
    sentiment: evaluation.sentiment,
    ruleCount: evaluation.criterion.length
  });
  
  // Track individual rule scores
  for (const rule of evaluation.criterion) {
    await analytics.track('VoiceQA Rule Score', {
      evaluationId: evaluation.evaluation_id,
      ruleName: rule.rule_name,
      category: rule.category,
      score: rule.score,
      maxScore: rule.max_score,
      percentage: Math.round((rule.score / rule.max_score) * 100)
    });
  }
  
  res.sendStatus(200);
});

Slack Alerts for Low Scores

javascript
app.post('/voiceqa-webhook', async (req, res) => {
  const { voiceQaResult } = req.body;
  const { evaluation } = voiceQaResult;
  
  // Alert if satisfaction is low
  if (evaluation.satisfaction < 5) {
    await slack.postMessage({
      channel: '#quality-alerts',
      text: `⚠️ Low satisfaction call detected`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*Low Satisfaction Alert*\n` +
                  `• Phone: ${evaluation.phone}\n` +
                  `• Satisfaction: ${evaluation.satisfaction}/10\n` +
                  `• Sentiment: ${evaluation.sentiment}\n` +
                  `• Intent: ${evaluation.intent}`
          }
        },
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*Improvement Needed:*\n${evaluation.suggestion}`
          }
        }
      ]
    });
  }
  
  res.sendStatus(200);
});

Database Storage

javascript
app.post('/voiceqa-webhook', async (req, res) => {
  const { voiceQaResult } = req.body;
  const { evaluation, transcript, inputs } = voiceQaResult;
  
  // Store evaluation in database
  await db.evaluations.create({
    data: {
      evaluationId: evaluation.evaluation_id,
      audioUrl: inputs.audio_file_url,
      phone: evaluation.phone,
      clientName: evaluation.clientName,
      transcript: transcript,
      intent: evaluation.intent,
      satisfaction: evaluation.satisfaction,
      sentiment: evaluation.sentiment,
      suggestion: evaluation.suggestion,
      conclusion: evaluation.conclusion,
      rules: evaluation.criterion,
      variables: voiceQaResult.variables,
      createdAt: new Date()
    }
  });
  
  res.sendStatus(200);
});

Security

Verify Webhook Source

Secure your endpoint by:

  1. IP Whitelisting — Only allow requests from FineGuide IPs
  2. URL Obscurity — Use a long, random URL path
  3. Payload Validation — Verify expected fields are present
javascript
app.post('/voiceqa-webhook-a8f3k2m9x7', (req, res) => {
  const { voiceQaResult } = req.body;
  
  // Validate payload structure
  if (!voiceQaResult?.evaluation?.evaluation_id) {
    return res.sendStatus(400);
  }
  
  // Process...
  res.sendStatus(200);
});

Troubleshooting

Webhooks Not Receiving Data

CheckSolution
URL accessible?Ensure endpoint is publicly reachable
HTTPS required?Use HTTPS endpoints
Correct department?Webhooks are per-department
URLs configured?Add webhook URLs in department settings

Incomplete Data

IssueCauseSolution
No transcriptTranscription stage webhookWait for evaluation webhook
Empty criterionEvaluation not completeWait for full processing
Missing phoneNot detected in callExpected if caller ID unavailable

Testing Webhooks

  1. Use webhook.site or requestbin.com
  2. Add the test URL to department settings
  3. Upload or process a test call
  4. View the received payload

Next Steps