Guide

Get call results

After every call Voximate writes a summary, an outcome, the caller’s sentiment and action items. Here is how to get them — and the transcript — into your own system.

What you get

A finished call carries an analysis object a few seconds after it ends. Until then — and on every item in a list — analysis is null.

GET /calls/{call_id} · 200
{
  "object": "call",
  "id": "3c27f11c-8a1e-4d55-9b0e-1f4c2a7d9e10",
  "agent_id": "7c3f1e90-5a44-4b21-9c07-2ed5b8f41a66",
  "direction": "outbound",
  "channel": "phone",
  "status": "completed",
  "from": "+4721000000",
  "to": "+4712345678",
  "started_at": "2026-10-12T10:04:12.000Z",
  "ended_at": "2026-10-12T10:07:33.000Z",
  "duration_seconds": 201,
  "end_reason": "caller_hangup",
  "metadata": {
    "crm_contact_id": "48213"
  },
  "has_recording": true,
  "analysis": {
    "summary": "The caller booked a check-up on Tuesday 14 October at 12:00.",
    "outcome": "resolved",
    "sentiment": "positive",
    "action_items": [
      "Send booking confirmation by SMS"
    ],
    "created_at": "2026-10-12T10:07:51.000Z"
  }
}
  • analysis.summary — what happened, in a few sentences.
  • analysis.outcome — for example resolved, escalated or abandoned.
  • analysis.sentiment — positive, neutral or negative.
  • analysis.action_items — follow-ups the agent promised or the caller asked for.

Webhook or polling

Webhook (call.completed)Polling (GET /calls)
SpeedSeconds after the analysis is readyAs often as you poll
RequestsOne per call, sent to youMany, most of them returning nothing new
NeedsA public https endpointNothing but a key
Best forLive integrations and CRMsNightly reports and backfills
Use both: webhooks for the live flow, and a nightly GET /calls?created_after=… to fill any gap while your endpoint was down.

Polling

List the calls since your last run, then fetch each one for its analysis and its transcript. Lists return analysis: null to stay fast.

curl "https://api.voximate.no/public/v1/calls?created_after=2026-10-12T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $VOXIMATE_API_KEY"

curl "https://api.voximate.no/public/v1/calls/3c27f11c-8a1e-4d55-9b0e-1f4c2a7d9e10" \
  -H "Authorization: Bearer $VOXIMATE_API_KEY"

curl "https://api.voximate.no/public/v1/calls/3c27f11c-8a1e-4d55-9b0e-1f4c2a7d9e10/transcript" \
  -H "Authorization: Bearer $VOXIMATE_API_KEY"

Example: notes in your CRM

Start each call with your CRM’s contact id in metadata. When call.completed arrives, it is right there on data.object.metadata: write the summary, the outcome and the transcript to that contact. crm.addNote stands in for your CRM’s own API.

server.js
// npm install express
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const API = 'https://api.voximate.no/public/v1';
const KEY = process.env.VOXIMATE_API_KEY;
const SECRET = process.env.VOXIMATE_WEBHOOK_SECRET;
const seen = new Set(); // use your database in production

function verified(req) {
  const parts = Object.fromEntries((req.get('Voximate-Signature') ?? '').split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.`).update(req.body).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

app.post('/voximate/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verified(req)) return res.sendStatus(400);
  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200); // acknowledge first

  if (seen.has(event.id)) return;
  seen.add(event.id);
  if (event.type === 'call.completed') void saveToCrm(event.data.object);
});

async function saveToCrm(call) {
  const contactId = call.metadata.crm_contact_id; // what you sent with POST /calls
  if (!contactId) return;

  const res = await fetch(`${API}/calls/${call.id}/transcript`, {
    headers: { Authorization: `Bearer ${KEY}` },
  });
  const transcript = (await res.json()).data
    .map((line) => `${line.role === 'agent' ? 'Agent' : 'Caller'}: ${line.text}`)
    .join('\n');

  await crm.addNote(contactId, {
    title: `Call ${call.analysis?.outcome ?? call.status} (${call.duration_seconds ?? 0}s)`,
    body: `${call.analysis?.summary ?? ''}\n\n${transcript}`,
    tasks: call.analysis?.action_items ?? [],
  });
}

app.listen(3000);

Read more about signatures, retries and duplicates on Webhooks.