NaCaller API Reference
Verify Indian mobile numbers in two HTTP calls with the NaCaller API
NaCaller has two ways to prove a user holds the phone number they typed. Miss OTP: they give a missed call to a number we show, and the caller ID is the proof — nothing is sent to the phone, so there is no SMS, no DLT registration and no template to get approved. Voice OTP: we ring them and read a 6-digit code aloud, for users who can’t give a missed call. Same key, same base URL, same webhook.
Building with an AI coding tool?
Paste this prompt into Lovable, Bolt, Cursor, Replit, v0 or Claude Code
It tells your tool exactly what to build: the server endpoints, where the key lives, what to show the user, and the voice fallback. Put your key in NACALLER_KEY first (step 1 below), then paste.
Add mobile-number verification to this app using the NaCaller API.
Miss OTP (missed call) is the main method; Voice OTP (we call the user and read a code) is the fallback.
Full reference: https://nacaller.com/docs — plain-text summary: https://nacaller.com/llms-full.txt
SECRETS — read these carefully
- The API key is in the environment variable NACALLER_KEY (it looks like nck_live_…).
- It is a SERVER secret. Never put it in client code, a VITE_/NEXT_PUBLIC_ variable, or the browser bundle.
Every NaCaller call must go through my backend (an API route, server action, or edge/serverless function —
on Supabase use an Edge Function and store the key with `supabase secrets set`).
BASE URL: https://api.nacaller.com/api/verify
AUTH HEADER on every call: Authorization: Bearer <NACALLER_KEY>
Errors are JSON: { "error": "human-readable message" } with a normal HTTP status.
BUILD THESE SERVER ENDPOINTS (names are yours to choose):
1. POST /verify/start { phone }
- Validate phone: keep the last 10 digits; must match /^[6-9]\d{9}$/ (Indian mobile).
- Call POST https://api.nacaller.com/api/verify/start with JSON { "phone": "<10 digits>", "reference": "<my user id>" }.
- Response: { requestId, phone, callNumber, status: "pending", expiresAt, expiresIn, instruction }.
- Save requestId against the signed-in user/session on the server. Return callNumber, instruction, expiresAt, requestId.
2. GET /verify/status/:requestId
- Only for a requestId saved for this user. Call GET https://api.nacaller.com/api/verify/status/<requestId>.
- Response: { requestId, phone, reference, status: "pending" | "verified" | "expired", verified, verifiedAt, expiresAt }.
- When verified is true, mark this user's phone as verified IN MY DATABASE (server side) and return { verified: true }.
3. POST /verify/voice { phone } (the fallback)
- Call POST https://api.nacaller.com/api/verify/start with { "phone", "reference", "method": "voice", "language": "en" } ("hi" for Hindi, "te" for Telugu).
- Response: { id, method: "voice", status: "calling", expiresAt }. Save id against the user.
- We ring the number and read a 6-digit code twice. The verification is valid for 10 minutes from /start. Until the user answers, /check returns 400 "not answered yet" (no attempt used) and /status says "pending".
4. POST /verify/check { id, code }
- Call POST https://api.nacaller.com/api/verify/check with { "id", "code" }.
- 200 { verified: true, id, phone, reference } → mark the phone verified server side.
- 400 { verified: false, error, attemptsLeft } → show error and attemptsLeft. 5 attempts max.
- 410 → code expired; offer to call again.
UI (one screen, mobile first)
- Step 1: a phone input ("Your mobile number", +91 prefix shown, 10 digits) and a "Verify" button.
- Step 2 (missed call): show the instruction text, e.g. "Give a missed call to <callNumber> from <phone>. You can cut the call as soon as it rings."
Show callNumber large, as a tel: link ("Tap to call") on phones. Show a countdown to expiresAt (5 minutes).
Poll my status endpoint every 2–3 seconds; stop on verified, expired, or when the user leaves the screen.
On verified: a success state and continue. On expired: "Time ran out" + a "Try again" button.
- After ~20 seconds on step 2, show a secondary button: "Can't give a missed call? Get a call instead".
It calls my /verify/voice endpoint, then shows a 6-digit code input (inputmode="numeric",
autocomplete="one-time-code") with the text "We're calling <phone> now. Enter the 6-digit code you hear."
- Show the API's error message as-is for 400, 402 (out of credits — also log it for me), 429 (too many attempts; ask them to wait).
- Disable buttons while a request is in flight. Never show or log the API key.
OPTIONAL: instead of polling, receive a webhook. NaCaller POSTs JSON
{ event: "verification.verified", requestId, phone, reference, verifiedAt, timestamp } (voice ones also carry method) to my HTTPS callback URL
with header X-MissOTP-Signature = hex HMAC-SHA256 of the RAW request body, keyed with the signing secret in NACALLER_WEBHOOK_SECRET.
Verify it with a constant-time compare against the raw bytes (not re-serialised JSON), reject if timestamp
(milliseconds) is more than 5 minutes old, then reply 2xx. Keep polling as a backup.
Do not invent other NaCaller endpoints or fields. Write the code, then list the env vars I need to set.The one thing to check in what it writes: the key must only ever be read on the server. If you see it in a file that runs in the browser, or in a variable prefixed VITE_ or NEXT_PUBLIC_, ask the tool to move the call into a server route.
1. Get a key
- Sign up — no card.
- In the dashboard, open Integrations → Miss OTP and add your site’s domain.
- Copy the key (it looks like
nck_live_…). It is shown once; we only keep its fingerprint. Lost it? Rotate it on the same card — the old key stops working immediately. - Store it as a server environment variable,
NACALLER_KEY. Never ship it to the browser or a mobile app.
Each site you add gets its own key, credit balance, callback URL and signing secret.
2. Base URL and auth
Every endpoint lives under one base URL, and every call carries the key as a bearer token:
https://api.nacaller.com/api/verify
Authorization: Bearer nck_live_…3. Errors
Errors come back as JSON with a normal HTTP status and one readable message you can show to the user as-is:
{ "error": "Enter a valid 10-digit Indian mobile number." }| Status | Meaning |
|---|---|
400 | The request is wrong — most often a number that is not a 10-digit Indian mobile. |
401 | Missing or invalid API key, or the site's key was switched off. |
402 | Out of credits for that method. Top up in the dashboard. |
404 | Unknown verification id (or one that belongs to another site). |
410 | Voice OTP only: the code has expired. |
429 | A rate limit was hit. The message says which; wait and try again. |
503 | Miss OTP only: no verification line is configured for your site yet. |
500 | Our fault. Safe to retry. |
4. Rate limits
- Miss OTP start: 120 requests a minute per key, and 5 per phone number every 10 minutes.
- Status reads: 600 a minute per key — plenty for polling every 2–3 seconds.
- Voice OTP: 3 calls per number an hour, 10 a day, and 5 code attempts per call.
Over a limit you get 429 with a message saying which one.
5. Test with free credits
Your account comes with 25 free verifications and 5 voice calls, once — they land on your first site; sites you add later start empty. There is no sandbox: the free credits run on the live line, so what you test is what you ship. Use your own phone.
Reference
- Miss OTP — Start a missed-call verification, show the number, poll status or take the webhook. Every field, status code and limit, with curl, Node and Python samples.
- Voice OTP — We call the number and read a 6-digit code aloud. Start, check and status endpoints, limits and billing, with curl, Node and Python samples.
- Webhooks — The signed verification callback (X-MissOTP-Signature, HMAC-SHA256) with Node and Python verifiers, and the inbound webhook that posts leads into NaCaller.
AI assistants can read the whole API in plain text at /llms-full.txt.
Stuck on the NaCaller API? Ask us.
A developer reads every message and replies by email, usually within one business day.