Launch offerYour ₹1,499 setup fee is on us — use code WELCOMEFREE at checkout. Ends 10 Oct.Start free

NaCaller API Reference

Get every finished call into your own system

When a call ends, NaCaller can send you what it left behind: the outcome, the details your campaign asked the agent to find out, the recording link and the transcript. Two ways, and you can use both at once:

  • A webhook: we POST JSON to your server. Best for a CRM, a database or an automation tool.
  • A Google Sheet: every call becomes a row. No code at all.

Building with an AI coding tool?

Paste this prompt into Cursor, Lovable, Bolt, Replit, v0 or Claude Code

Prompt for your AI coding tool
Add an endpoint to this app that receives finished-call results from NaCaller.
Full reference: https://nacaller.com/docs/call-results — plain-text summary: https://nacaller.com/llms-full.txt

WHAT NACALLER DOES
- When a call ends, NaCaller waits about 45 seconds (so the transcript and outcome are ready) and then POSTs JSON to my HTTPS URL.
- Body: { event: "call.completed", call: { id, direction, status, outcome, outcomeReason, startedAt, completedAt, durationSeconds, to, from, recordingUrl, transcript, captured, tags }, contact: { id, name, phone } | null, campaign: { id, name } | null, agent: { id, name } | null }.
- "captured" is an object of the fields my campaign asked the agent to find out (for example { course: "B.Tech CSE" }); it can be empty.
- Header X-NaCaller-Signature: "sha256=" + hex HMAC-SHA256 of the RAW request body, keyed with the signing secret from NaCaller → Settings. Store that secret in NACALLER_CALL_SECRET.

BUILD
1. A POST route at a public HTTPS URL that reads the RAW body (not parsed JSON).
2. Verify the signature with a constant-time compare. Reject with 401 if it does not match.
3. De-duplicate on call.id (a retry after a failure can deliver the same call twice).
4. Save what I need (outcome, captured, recordingUrl, transcript), then reply with any 2xx within 8 seconds. Anything else is retried 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours later, then given up.

RULES
- The URL must be https and a public host. Redirects are not followed. Localhost does not work: use a tunnel while developing.
- Never log the signing secret. Do not invent other NaCaller endpoints or fields.
Write the code, then list the env vars and the URL I must paste into NaCaller → Settings → "Send call results".

The webhook

1. Switch it on

  1. Sign in and open Settings, then the card Send call results to your CRM or sheet.
  2. Paste your HTTPS URL and save. A signing secret appears: store it as NACALLER_CALL_SECRET on your server.
  3. Press Send a sample call to check it reaches you.

The URL must be https and a public host. Redirects are not followed, and localhost does not work, so use a tunnel while you build.

2. What we send

A POST with a JSON body, about 45 seconds after the call ends so the transcript and outcome are ready. Calls that were never answered are sent sooner and carry a status such as No Answer.

Request body
{
  "event": "call.completed",
  "call": {
    "id": "clx9k2m0a0001",
    "direction": "outbound",
    "status": "Completed",
    "outcome": "interested",
    "outcomeReason": "Asked to book a counselling slot on Saturday.",
    "startedAt": "2026-10-03T07:12:04.000Z",
    "completedAt": "2026-10-03T07:14:31.000Z",
    "durationSeconds": 147,
    "to": "919876543210",
    "from": null,
    "recordingUrl": "https://…",
    "transcript": "Agent: Namaskaram! …\nCustomer: …",
    "captured": { "course": "B.Tech CSE", "hostel": "Needed, Guntur" },
    "tags": ["wants site visit"]
  },
  "contact":  { "id": "cld8…", "name": "Ravi", "phone": "9876543210" },
  "campaign": { "id": "clc7…", "name": "October admissions" },
  "agent":    { "id": "cla6…", "name": "Priya" }
}
FieldMeaning
eventAlways call.completed.
call.idA stable id. Use it to de-duplicate.
call.directionWhether we called out (outbound) or answered a call (inbound).
call.statusHow the call ended, for example Completed, Busy or No Answer.
call.outcomeOur reading of the call, for example interested, callback, not_interested or do_not_call. null when it could not be judged.
call.outcomeReasonOne sentence on why.
call.durationSecondsConnected time in seconds. 0 if never answered.
call.to / call.fromThe numbers involved, digits only. from is set on inbound calls.
call.recordingUrlA link to the recording, or null.
call.transcriptThe whole conversation as text, or null.
call.capturedAn object of the details your campaign asked the agent to find out, such as { "course": "B.Tech CSE" }. Empty if none were set. Choose them when you create a campaign.
call.tagsShort labels the analysis added.
contact / campaign / agentWho was called, which campaign it belongs to and which agent spoke. Each is null when it does not apply.

3. Check that it is really us

Every request carries X-NaCaller-Signature: sha256=<hex>, the HMAC-SHA256 of the raw request body keyed with your signing secret. Compare in constant time against the raw bytes, not re-serialised JSON. This is a different header from the verification callback, so do not reuse that check.

Node.js (Express)
// Express. Verify against the RAW body, not re-serialised JSON.
import crypto from "node:crypto";

function verify(raw, header, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(raw).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(header || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/nacaller/calls", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.get("X-NaCaller-Signature"), process.env.NACALLER_CALL_SECRET)) return res.sendStatus(401);
  const evt = JSON.parse(req.body);          // evt.event === "call.completed"
  // A retry can deliver the same call twice: de-duplicate on evt.call.id.
  saveCall(evt);                             // your code
  res.sendStatus(200);                       // any 2xx means delivered
});
Python (Flask)
# Flask. Verify against the RAW body, not re-serialised JSON.
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)

def verify(raw: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")

@app.post("/nacaller/calls")
def calls():
    if not verify(request.get_data(), request.headers.get("X-NaCaller-Signature"), os.environ["NACALLER_CALL_SECRET"]):
        abort(401)
    evt = request.get_json()                 # evt["event"] == "call.completed"
    # A retry can deliver the same call twice: de-duplicate on evt["call"]["id"].
    save_call(evt)                           # your code
    return "", 200

4. Reply, and what happens if you cannot

  • Answer with any 2xx within 8 seconds. That counts as delivered.
  • Anything else (an error code, a timeout, a network failure) is retried 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours later, then given up. That is 6 attempts in all.
  • Because a retry can follow a response that was slow rather than failed, the same call can arrive twice. De-duplicate on call.id.
  • Settings shows how many deliveries are waiting or have been given up, with the latest error.

Google Sheets

Every finished call is added as a new row in a sheet you own. There is no Google sign-in, and we can only touch a sheet you have deliberately shared with us.

  1. Open Settings, then the card Google Sheets.
  2. Share your sheet with the address shown there, as an Editor.
  3. Paste the sheet link and press Connect. Use Add a sample row to check.

If the Google Sheets card is not in your Settings, it is not switched on for your account yet. Ask us and we will enable it.

We add a header row if the sheet is empty, then one row per call with these columns:

Columns
Date (IST)	Contact	Phone	Direction	Status	Outcome	Why	Duration (sec)	Campaign	Agent	Captured	Tags	Recording	Transcript

Captured holds all your campaign’s fields in one cell, for example course: B.Tech CSE · hostel: Needed, because every campaign asks different questions and a sheet’s columns are fixed once written. Long transcripts are cut to fit a cell. A sheet that cannot be reached is retried on the same schedule as the webhook.

Related

Stuck on the NaCaller API? Ask us.

A developer reads every message and replies by email, usually within one business day.

Call meStart free