LLOQU
ProductPricingBlogDocsLogin

On this page

What does Twilio error 31002 mean?Where do I find the real cause of a 31002?Why do I get 31002 when the call still goes through?What causes Twilio 31002?Is it 31002, 31000, 31003, or 31005?The 31002 triage checklistFAQThe configuration that causes this, deleted
All articles
TwilioVoice SDKErrors

Twilio Error 31002 Connection Declined: How to Fix It

Twilio error 31002 means the connection was declined but never says why. Find the paired error in the Debugger, then fix the five causes behind it.

Alloqui TeamAug 13, 20269 min read
On this page
What does Twilio error 31002 mean?Where do I find the real cause of a 31002?Why do I get 31002 when the call still goes through?What causes Twilio 31002?Is it 31002, 31000, 31003, or 31005?The 31002 triage checklistFAQThe configuration that causes this, deleted
Back to all articles
LLOQUAlloqui

Product

  • Features
  • Pricing
  • Blog

Developers

  • Documentation
  • @alloqui/dialer on npm

Company

  • Contact

Legal

  • Privacy
  • Terms

© 2026 Alloqui

Twilio error 31002 ("Connection declined") means Twilio's signaling layer refused your Voice SDK call attempt — the request reached Twilio, and Twilio said no. The code carries no diagnosis of its own. Twilio's Voice SDK error reference lists it in full as: "Connection declined. Check the debugger for details about the underlying cause." The real reason is a second error logged server-side, and every useful fix starts with going to get it.

What does Twilio error 31002 mean?

31002 belongs to Twilio's 310xx Voice SDK series, and it is a client-side symptom of a server-side refusal. Your browser (or iOS/Android app) sent a valid signaling request over an open WebSocket, Twilio evaluated it against your account and your TwiML Application, and rejected it before any media was set up. That distinguishes it from the codes around it: 31005 and 53000 mean the connection itself broke, while 31002 means the connection worked fine and the call was refused.

The code is identical across SDKs — the Android SDK exposes it as the CallException constant CONNECTION_DECLINED, and the JavaScript SDK surfaces it in the error event with the message "Connection Declined". Same cause, three wrappers.

Where do I find the real cause of a 31002?

Open the Twilio Debugger before you touch any code: Console → Monitor → Logs → Errors. Find the entry matching your failed call's timestamp and open it. In almost every case there is a paired error there with an actual diagnosis attached, and that is the code you should be debugging.

These are the ones that pair with 31002 most often:

Paired error in DebuggerWhat it meansWhere the fix lives
13227"You attempted to initiate an outbound phone call to a phone number that is not enabled on your account"Voice Geographic Permissions
21219The To number isn't verified — trial accounts onlyVerify the number, or upgrade
11200HTTP retrieval failure: your Voice URL returned non-2xx, timed out, or was unreachableYour webhook server
12100Document parse failure — Twilio couldn't parse your TwiML as XMLYour webhook's response body
31201 / 31204Authorization failure or invalid JWTYour token-minting code
31000Generic error, no classification availableFollow the 31000 playbook

One more Debugger result deserves naming, because it gives the fastest diagnosis on this page: no entry at all. If the failed call produced 31002 in your browser console but nothing in the Debugger, your credentials are pointing somewhere you aren't looking. That is the rule of thumb from the top-voted answer on the canonical Stack Overflow thread for this error — no activity in the Debugger means the wrong account SID, the wrong auth token, or the right credentials on the wrong subaccount. Check the project selector at the top-left of the Console and the subaccount selector at the top-right before you go any further.

On the SDK side, log the full error object rather than the code:

device.on('error', (twilioError, call) => {
  console.log(twilioError.code);          // 31002
  console.log(twilioError.description);   // Connection declined
  console.log(twilioError.originalError); // occasionally names the real failure
});

Why do I get 31002 when the call still goes through?

This is the strangest 31002 report, and it has a precise explanation. The symptom: your browser throws "31002: Connection Declined", and your phone rings anyway and plays audio. Both things are true because there are two different calls.

Here is the sequence. Your client calls device.connect(). Twilio sends a webhook to the Voice URL on your TwiML App and waits for TwiML telling it what to do with the browser leg. If your handler never returns TwiML — because it uses the REST API to originate a separate outbound call instead, a mistake a Twilio developer evangelist diagnosed on exactly this Stack Overflow question — then the REST-originated call rings your phone while the browser leg gets nothing back and is declined. You see a working call and a failing one, and they were never the same call.

The tell is in your webhook handler. It should build TwiML and send it, not call client.calls.create():

app.post('/voice', (req, res) => {
  const twiml = new twilio.twiml.VoiceResponse();
  twiml.dial({ callerId: process.env.TWILIO_NUMBER }, req.body.To);
  res.type('text/xml');
  res.send(twiml.toString());
});

A related variant: the browser leg connects, then drops seconds later. That is a different failure with its own causes — see why Twilio calls connect and then disconnect.

What causes Twilio 31002?

Five causes account for nearly all of them. Work them in this order, since the first two take under a minute each to rule out.

1. Trial account, unverified destination

Trial accounts can only call numbers you have verified as your own. Dial anything else and Twilio declines the connection; the browser reports 31002, the Debugger reports 21219 ("'To' phone number not verified"). Error 10002 shows up here too when a trial account reaches for a feature reserved for upgraded accounts.

Confirm it: Look for the trial banner at the top of the Console. Then check Phone Numbers → Manage → Verified Caller IDs for the exact E.164 number you dialled. Fix by verifying that number, or by upgrading the account.

2. Geographic permissions disabled for the destination country

Twilio ships with a restricted set of destination countries enabled and blocks the rest as fraud protection, so a dialer that works perfectly for US numbers fails the moment someone tries the UK, India, or Nigeria. The paired error is 13227, whose documented solution is a single line: "Please check your Voice Dialing Geographic Permissions, fix it, and try again."

Confirm it: Console → Voice → Settings → Geo Permissions. Find the destination country in the list and check that the box is ticked. This one has a distinctive signature in your own logs — 31002 clustered by country code rather than by user or by time.

3. The TwiML App's Voice URL is wrong or erroring

For anyone past the trial stage, this is the most common cause by a wide margin, and it has more failure modes than the others combined. Every one of the top Stack Overflow reports for 31002 resolved here: a stale ngrok tunnel, an old deployment, a URL typo, a handler returning HTML instead of XML.

The sharpest instance is worth memorising, because nothing about the symptom points at it. One developer's accepted fix, after hours of debugging, was that the Voice URL was set to http:// rather than https:// — the server issued a 301 redirect to the secure URL, and TwiML Apps do not follow 301 redirects. The request simply fails, and the browser sees 31002.

Confirm it: Go to Console → Voice → TwiML → TwiML Apps, open the app your token points at, and copy the Voice Request URL. Then call it the way Twilio does:

curl -i -X POST https://your-server.com/voice \
  -d "To=+15551234567" -d "From=client:agent"

You need HTTP 200 with no redirect hop, a Content-Type of text/xml or application/xml, and valid TwiML in the body. A 500, an HTML error page, a login redirect, or a slow response is your bug. Debugger errors 11200 and 12100 both land in this bucket.

4. Wrong app SID or missing voice grant in the token

Your access token has to carry a VoiceGrant whose outgoingApplicationSid names the TwiML App you just tested. Point it at a deleted app, an app in a different Twilio account, or omit the grant, and outbound calls are declined at setup:

const voiceGrant = new VoiceGrant({
  outgoingApplicationSid: process.env.TWILIO_TWIML_APP_SID, // must start with AP
  incomingAllow: true,
});

The cross-account version of this catches teams building multi-tenant dialers: a TwiML App lives in one account, and it can't be referenced from a token minted against another. Each account needs its own app.

Confirm it: Paste the token into jwt.io and read the grants.voice.outgoing.application_sid claim. Compare it character by character against the SID in the Console. If the SID matches but you're still declined, the problem is the credentials that signed the token rather than the grant — that path is covered in Twilio JWT token expired and invalid-token errors.

5. Suspended or underfunded account

An account suspended for a billing failure, or one whose balance hit zero, declines new call attempts while the SDK connects normally. This is rare, and it announces itself the same way for every user at once.

Confirm it: The Console dashboard shows the balance and any suspension notice on the front page. Twilio also emails the account owner before suspending, so check that inbox.

Is it 31002, 31000, 31003, or 31005?

The neighbouring codes get confused with 31002 constantly, and telling them apart narrows the search enormously.

CodeWhat actually happenedFirst thing to check
31002Signaling worked; Twilio refused the callDebugger, for the paired error
31000Something failed and the SDK couldn't classify itoriginalError, then the Debugger
31003The connection attempt timed out with no answerNetwork path, firewall, edge location
31005The WebSocket dropped unexpectedly, usually mid-callNetwork stability, webhook failures mid-call

If you are seeing 31002 and 31000 together, debug 31002 — it is the more specific of the two and names the layer that failed.

The 31002 triage checklist

  1. Open Monitor → Logs → Errors in the Console and find the paired error. If a real code appears, stop and fix that code.
  2. No entry in the Debugger at all? You are on the wrong account or subaccount — check the project and subaccount selectors.
  3. Check whether the account is in trial mode and whether the destination number is verified.
  4. Check Voice → Settings → Geo Permissions for the destination country.
  5. curl -X POST the TwiML App's Voice URL with To and From params. Demand a 200, XML content type, and no redirect.
  6. Decode the token and confirm application_sid matches the app you just tested.
  7. Read your webhook handler and confirm it returns TwiML rather than originating a REST call.

FAQ

Does 31002 mean the person I called declined the call? No. A callee who rejects or ignores a call produces a call status of busy or no-answer, not a Voice SDK error. 31002 is raised against your client's connection request, and it usually fires before the destination number is ever dialled — which is why the Debugger, not the call log, holds the answer.

Can an invalid phone number cause Twilio 31002? Yes, and it is a common one in staging. Numbers left as test data (the 555 range), numbers missing the + and country code, or anything not in E.164 will be rejected. Log the exact To value your client sends before device.connect() — the number in your UI and the number on the wire often differ.

How do I fix 31002 in the Twilio iOS and Android SDKs? Identically to the web. On Android it arrives as CallException code 31002 with the constant CONNECTION_DECLINED; on iOS it comes through the call delegate's failure callback. The cause is server-side in both cases, so the Debugger check and the five causes above apply without change.

Why do I only get 31002 in production? Environment drift, in three usual shapes: production points at a different TwiML App SID, production credentials belong to a subaccount with different geo permissions, or the production Voice URL is http:// and redirects. Diff the environment variables of both deployments, then compare the two TwiML Apps side by side in the Console.

Should my app retry after a 31002? Not automatically. 31002 is deterministic — a declined connection stays declined until the configuration changes, so retrying floods Twilio and produces nothing. Surface the failure to the user, log the Call SID, and reserve retry logic for the transport-level codes (31005, 53000) where a second attempt genuinely can succeed.

The configuration that causes this, deleted

The middle of that cause list — TwiML App Voice URL, app SID mismatch, missing voice grant — is configuration you had to build and then keep working forever. Alloqui removes it. You bring your own Twilio API key; we provision the TwiML App, host the voice webhooks, and mint tokens against that same app, so the SIDs always match and the Voice URL is always up. Paste your keys, drop <Dialer /> from @alloqui/dialer into your React app, place a call.