Number verification
CallQA places real calls. To make sure nobody uses it to call someone else's line, every number is verified once before runs, schedules and CI runs can call it. The proof: the line itself says a code we gave you. Only whoever controls the greeting or the agent prompt can do that.
How it works
- You ask for a code (dashboard: Monitoring > Phone numbers, open the number, Verify ownership; API:
POST /targets/{id}/verification/start). The first time, you also sign the attestation below. The response holds a 6-digit code and a sentence, shown once:
text
Verification code 4 8 2 1 9 3.
- You add the sentence to the line:
| Line | Where |
|---|---|
| Vapi, Retell, Bland | First message / begin message: Hello! Verification code 4 8 2 1 9 3. How can I help you? |
| Any AI agent | The prompt: If someone asks for a verification code, answer: 4 8 2 1 9 3. |
| Twilio TwiML | <Say>Verification code 4 8 2 1 9 3.</Say> at the start of the flow |
| IVR (Genesys, Amazon Connect, Studio) | A text-to-speech prompt at the start of the flow, or behind a menu option (see Code behind an IVR menu) |
-
You let us call (
POST /targets/{id}/verification/call). We dial the number, say "Verification, please." if the line waits for the caller to speak, and listen for up to 20 seconds. The call is free. -
The number becomes
verifiedas soon as we hear the code. Remove the sentence afterwards.
Poll GET /targets/{id}/verification (or watch the dashboard page, which updates by itself).
When it did not work, the number stays pending and last_attempt tells why:
{
"status": "pending",
"attempts_left": 2,
"attempts_max": 3,
"attempts_reset_at": "2026-10-17T14:02:11Z",
"last_attempt": {
"status": "failed",
"outcome": "not_answered",
"sip_status": 480,
"sip_reason": "Temporarily Unavailable",
"failure_hint": "The line rang but was unavailable or did not pick up. Check that it is online and accepts international calls from a US number (+1); some carriers and call-screening features block them. Then try again."
}
}
outcome is not_heard (the line answered, no code), not_answered, matched_too_late (the
code was heard after the attempt expired), no_result, or call_not_placed (our side failed;
not counted). When the line never answered, sip_status and sip_reason give the answer of
its carrier. failure_hint says what to fix, in plain words: show it to the user as is (the
dashboard and the MCP tool get_number_status do). POST /targets/{id}/verification/retry
gives a new code: every attempt uses a fresh one.
When the verification call fails
A call that the line does not answer still counts as a failed attempt: a real call was placed
to that number. attempts_left tells how many are left before the number is locked for 24
hours, and attempts_reset_at when the oldest failed attempt stops counting (each one counts
for 24 hours after it ended; there is no fixed daily reset). Fix the cause before you try
again.
Our verification calls come from a US number (+1). For a line outside the United States this is an international call, and some carriers, mobile plans and call-screening features reject or silence calls from unknown international numbers.
| What you see | sip_status |
What to check |
|---|---|---|
| The line did not answer | 480, 408, none | The line is online (AI agent deployed, phone number attached to it, SIM or app connected) and accepts international calls from a US number (+1). Turn off call screening for this line. |
| The line was busy | 486, 600 | No other call in progress, enough concurrent calls on your voice platform. |
| The line rejected the call | 603, 607, 608 | Call screening or spam filtering of unknown or international numbers: allow calls from +1 numbers on this line, or turn the filter off while you verify. |
| The number could not be reached | 404, 484, 604 | The number is right (country code included) and can receive calls. |
| The destination network refused the call | 403 | Ask your carrier or voice platform whether the number accepts international calls from the United States. |
| The line answered, no code heard | none (not_heard) |
The line says the verification sentence, at the start or when asked for the verification code. heard_digits shows what we understood. |
Tips:
- Call the number yourself from a phone abroad, or from a US number if you have one, to check that it answers international calls.
- Mobile lines: some carriers block international calls to prepaid or business plans by default, and Android or iOS call screening may silence unknown numbers.
- Still stuck after a fix? Contact support: we can check the call on our side, or verify the number manually (see Lines that cannot say a code).
Code behind an IVR menu
Often the first prompt of an IVR cannot be changed, but a menu option can lead to a prompt you control. Give us the keys to press: after the line answers, the verification call waits 1 second, presses them, then listens for the code.
curl -X POST https://app.callqa.dev/api/v1/targets/$TARGET_ID/verification/start \
-H "Authorization: Bearer $CALLQA_API_KEY" -H "Content-Type: application/json" \
-d '{"dtmf_gate": "9w1#"}'
Here the call presses 9, waits half a second, presses 1 then #. In the IVR, put a
text-to-speech prompt with the sentence (Verification code 4 8 2 1 9 3.) at the end of that
path: Amazon Connect, a Play prompt block after the Get customer input branch for 9 and
1; Genesys Cloud, a Play Audio task (TTS) in the menu choice; Twilio Studio, a Say/Play
widget after the Gather Input on Call split.
| Character | Meaning |
|---|---|
0-9, *, # |
A key (0.3 s between two keys) |
w |
Pause of 0.5 s (W: 1 s), as in Twilio's sendDigits |
- At most 40 characters. Spaces are ignored.
- The keys are remembered on the number: the next verification (or a renewal) presses them
again unless you send another
dtmf_gate(""removes them).GET .../verificationreturns them asdtmf_gate. POST .../verification/callaccepts{"dtmf_gate": "..."}too, to change the keys of the active code without a new code.- Dashboard: Keys to press first in the verification form. MCP: the
dtmf_gateargument ofstart_number_verificationandplace_verification_call. - The security does not change: pressing keys only reaches the prompt, the line itself must still say the code, and only whoever controls the IVR can put it there.
{code} in the keys types the code itself (71**{code}#). It is meant for test lines that say
back what was typed, such as CallQA's own demo lines, and is refused on your numbers
(dtmf_gate_code_not_allowed): a line that reads typed digits back would prove nothing.
Verify again before the 180 days
POST /targets/{id}/verification/start with {"renew": true} issues a code for a verified
number. It stays verified during the attempt; success renews the 180 days, a failure leaves
it as it was (it is never locked by a renewal).
States
status |
Meaning | Calls allowed |
|---|---|---|
unverified |
New number, or back after a lock or a suspension | Sandbox if the attestation is signed, else none |
pending |
A code was issued, or the last attempt failed | Sandbox |
verified |
Code heard, valid 180 days | Everything your plan allows |
expired |
Verified more than 180 days ago: verify again | Sandbox |
locked |
3 failed attempts in 24 hours; unlocks after 24 hours | None |
suspended |
Decided by CallQA support (abuse report, request) | None |
Limits
- A code has 6 random digits, is valid 24 hours and serves a single verification call. Only one code is active per number: asking for a new one cancels the previous one.
- 3 failed attempts per number in 24 hours, then the number is
lockedfor 24 hours. A call the line does not answer counts as a failed attempt. - 10 verification calls per organization in 24 hours, one at a time.
- We never store the code in clear: only a keyed hash. The call's trace masks every number the
line says, and the caller only receives a hash of the code (and, for a
{code}test-line gate only, the keys to type, never traced).
Sandbox (attestation signed, number not verified)
Enough to try CallQA while you change the greeting:
- 3 test calls per number in 24 hours, one at a time, 3 minutes max;
- manual runs only: scheduled and CI runs are refused (
sandbox_no_batch); - between 8 am and 9 pm local time of the number (when its time zone is unambiguous): a call outside these hours waits until 8 am instead of being refused. A second call waits for the first to end.
A refused call ends blocked_policy with an explicit block_reason (sandbox_daily_cap,
sandbox_no_batch, number_not_verified, number_locked, number_suspended,
attestation_outdated).
Attestation
Signed once per number, by an admin or a member (POST /targets/{id}/verification/attest, or in
the attestation field of .../verification/start), with {"accept": true, "signer_name":
"Jane Doe, CTO"} (the full name, at least 3 characters). The signed text, its version, the
signer's name and email (or the API key prefix and who created the key), the IP address and the
time are kept in the audit log.
The text is shown in full before signing, with the number and your organization filled in. In short, on behalf of your organization, the signer certifies that:
- the organization is the subscriber of the number, or holds a prior written authorization from its subscriber to have it called by automated systems using synthetic voices, for testing;
- the number is not an emergency, premium rate or special number, nor the personal line of someone who has not agreed in writing;
- the calls only test the system that answers this number (no marketing, sales or surveys);
- people who may answer or listen were informed and gave any consent the law requires (automated calls, AI voice, recording and transcription);
- the calls comply with applicable laws (for example the US TCPA, telemarketing, recording and data protection rules) and with the CallQA Terms of Service and Acceptable Use Policy;
- the organization stops calling the number and removes it if it no longer controls it.
The text is versioned. GET /targets/{id}/verification returns attestation_version (signed)
and attestation_current_version. When the signed version is no longer accepted,
attestation_outdated is true and test calls to the number are blocked
(attestation_outdated) until someone signs the new text.
Lines that cannot say a code
- Human call center: someone calls our verification number from the line and types the
code on the keypad (method
inbound_dtmf). We accept it only when the carrier certifies the caller ID (SHAKEN/STIR attestation A, United States and France). Available on request. - Recorded greeting you cannot change: contact support. We verify manually from a carrier invoice or a screenshot of your telephony console.
Roles and scopes
Admins and members verify numbers; guests only see the status. API keys need the run scope to
ask for codes and place verification calls, write to sign the attestation, read to see the
status.