Skip to content
Markets open
Quickstart · Verification

Verify a phone number by voice call

Some numbers cannot receive texts, and some users prefer a call. Pass channel "voice" and the same Verify API rings the number, reads the code twice and hangs up.
Calls
POST /verify/start · POST /verify/check
Scope
verify:write
Cost
The call that reads the code, at the route rate per billing increment plus 2% of the route rate, never more than $0.001. No text-to-speech surcharge and no per-verification fee.

Get an API key

Open an account, then create a key under Dashboard, API keys. Live keys (wmmn_live_sk_) send real traffic and charge your balance. Test keys (wmmn_test_sk_) run the same request through routing and pricing without delivering it, and draw on test credit, which comes with an invite code.

A key restricted to scopes needs verify:write. On a test key no call is placed and the start response includes testCode.

Every sample reads the key from the environment. Keep it out of source control.

export PACKETEXCHANGE_API_KEY=wmmn_live_sk_...

Fund the balance

PacketExchange is prepaid. Top up from $5 by card or crypto, or from $100 by wire, under Dashboard, Billing. An agent can top itself up with x402. There is no contract and no monthly fee.

Each start places one short call (capped at 120 seconds), billed like any API call. Unanswered calls cost nothing. Checks are free.

CostThe call that reads the code, at the route rate per billing increment plus 2% of the route rate, never more than $0.001. No text-to-speech surcharge and no per-verification fee.

Make the request

Start with channel "voice", a language (en, es, fr, de, pt or hi) and the caller ID the call should present in from. Then check the code exactly as for SMS.

# 1. Send the code. PacketExchange generates it, reads the code aloud on a call and keeps only a hash.
verification_id=$(curl -s https://packetexchange.io/api/v1/verify/start \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{"to": "+447700900123", "channel": "voice", "from": "+14155550100", "language": "en", "brand": "Acme"}' | jq -r .data.verificationId)

# 2. Check the code the user typed in. A wrong code is an answer ("denied"), not an error.
read -r -p "Code: " code
curl -s https://packetexchange.io/api/v1/verify/check \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"verificationId\": \"$verification_id\", \"code\": \"$code\"}" | jq .data
Save as request.sh, run bash request.shNeeds curl, plus jq for the multi-step samplesThe sample waits for you to type the code you heard.

Handle the response

Start returns as soon as the call is dialled: sendStatus is "initiated" and sendRef is the call id. Check answers the same way as for SMS.

200 OKPOST /verify/start
{
  "success": true,
  "data": {
    "verificationId": "<uuid>",
    "to": "+447700900123",
    "channel": "voice",
    "status": "pending",
    "expiresAt": "<ISO-8601 UTC>",
    "maxAttempts": 5,
    "sendRef": "<callId of the code call>",
    "sendStatus": "initiated",
    "createdAt": "<ISO-8601 UTC>"
  }
}
200 OKPOST /verify/check
{
  "success": true,
  "data": {
    "verificationId": "<uuid>",
    "status": "approved",
    "attemptsRemaining": <integer>
  }
}

Values in angle brackets are placeholders for your own ids, times and amounts.

language
en, es, fr, de, pt or hi for voice. SMS codes also support ar.
from
The E.164 caller ID the code call presents.
sendStatus
"initiated" for a voice start: the call is on its way. The final outcome is recorded when it ends.
status (check)
approved, denied, expired or max_attempts.

When something goes wrong

  • The same limits as SMS apply: 5 starts per number per hour, a 30-second resend cooldown and 1,000 per account per day.
  • Already generate your own codes? POST /comms/voice-otp reads a code you supply and returns a voice_otp id you can look up later.
  • Errors use the standard envelope: { "success": false, "error": { "code", "message", "details" } }.
400The error envelope
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "path": "to", "message": "<why the field was refused>" }
    ]
  }
}

Go further

Start building

Run it with your own key

Open an account, add credit from $5 and create a key. You pay the carrier's rate plus a fee capped at $0.001.