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:
- Navigate to Voice QA → Departments → [Your Department]
- Click Settings or the gear icon
- Scroll to the Webhooks section
- Add your webhook URLs
- Save changes
Webhooks are always active when URLs are configured.
When Webhooks Fire
Webhooks are triggered at two points in the evaluation process:
| Stage | When | What's Included |
|---|---|---|
| After Transcription | When transcript is ready | inputs, transcript, partial evaluation |
| After Evaluation | When full analysis is complete | Complete payload with all scores and analysis |
Both stages send to all configured webhook URLs.
Webhook Payload
All VoiceQA webhooks send the same payload structure:
{
"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
| Field | Type | Description |
|---|---|---|
voiceQaResult | Object | Container for all evaluation data |
Inputs Object
| Field | Type | Description |
|---|---|---|
audio_file_url | String | URL to the original call recording |
evaluation_id | String | Unique identifier for this evaluation |
type | String | Always "voice_qa_evaluation" |
user_id | String | Organization ID that owns this evaluation |
Evaluation Object
| Field | Type | Description |
|---|---|---|
evaluation_id | String | Matches inputs.evaluation_id |
phone | String | Customer phone number |
clientName | String | Customer name (if identified) |
intent | String | AI-detected customer intent/purpose of call |
satisfaction | Number | Customer satisfaction score (0-10) |
sentiment | String | Overall sentiment: "positive", "negative", or "neutral" |
criterion | Array | Rule-by-rule evaluation results |
suggestion | String | Overall improvement recommendations |
conclusion | String | Summary conclusion of call quality |
Criterion Array Items
Each item in the criterion array represents one evaluation rule:
| Field | Type | Description |
|---|---|---|
rule_id | String | Unique rule identifier |
rule_name | String | Human-readable rule name |
category | String | Rule category (e.g., "general", "sales", "customer_service") |
score | Number | Achieved score for this rule |
max_score | Number | Maximum possible score (depends on rule type) |
evidence | String | Quote or observation from the call supporting the score |
analysis | String | AI explanation of why this score was given |
suggestion | String | Improvement recommendation (null if score is perfect) |
Variables Object
Custom variables extracted during evaluation (configured per-department):
| Example Variable | Type | Description |
|---|---|---|
order_number | String | Extracted order/case numbers |
product_mentioned | String | Products discussed in call |
callback_requested | Boolean | Whether customer requested callback |
upsell_opportunity | Boolean | Whether sales opportunity was identified |
Integration Examples
Basic Webhook Handler
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
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
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
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
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:
- IP Whitelisting — Only allow requests from FineGuide IPs
- URL Obscurity — Use a long, random URL path
- Payload Validation — Verify expected fields are present
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
| Check | Solution |
|---|---|
| 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
| Issue | Cause | Solution |
|---|---|---|
| No transcript | Transcription stage webhook | Wait for evaluation webhook |
| Empty criterion | Evaluation not complete | Wait for full processing |
| Missing phone | Not detected in call | Expected if caller ID unavailable |
Testing Webhooks
- Use webhook.site or requestbin.com
- Add the test URL to department settings
- Upload or process a test call
- View the received payload
Next Steps
- Integration → — Set up call recording sync
- Departments → — Configure evaluation rules
- AI Assistant Webhooks → — Chat/messaging webhooks