{"record":{"id":"1039365968ea01e1","repo":"phacility/phabricator","slug":"expected-json-response-from-twilio","errorCode":null,"errorMessage":"Expected JSON response from Twilio.","messagePattern":"Expected JSON response from Twilio\\.","errorType":"exception","errorClass":"PhutilProxyException","httpStatus":null,"severity":"error","filePath":"src/applications/metamta/future/PhabricatorTwilioFuture.php","lineNumber":92,"sourceCode":"      }\n\n      $this->future = $future;\n    }\n\n    return $this->future;\n  }\n\n  protected function didReceiveResult($result) {\n    list($status, $body, $headers) = $result;\n\n    if ($status->isError()) {\n      throw $status;\n    }\n\n    try {\n      $data = phutil_json_decode($body);\n    } catch (PhutilJSONParserException $ex) {\n      throw new PhutilProxyException(\n        pht('Expected JSON response from Twilio.'),\n        $ex);\n    }\n\n    return $data;\n  }\n\n}\n","sourceCodeStart":74,"sourceCodeEnd":101,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/metamta/future/PhabricatorTwilioFuture.php#L74-L101","documentation":"PhabricatorTwilioFuture wraps the Twilio REST API and expects JSON back: it first rethrows the HTTP status if isError(), then decodes the body with phutil_json_decode(). The status was fine but the body was not JSON, so it throws PhutilProxyException chaining the parser error. Something on the path - an authenticating proxy, TLS interception, a captive portal, or a misrouted host - returned HTML or plain text instead of a Twilio API payload.","triggerScenarios":"SMS delivery through PhabricatorMailTwilioAdapter::sendMessage() (mail workers, 'bin/mail send-test' with an SMS message) or direct use of PhabricatorTwilioFuture while an intercepting proxy rewrites responses; wrong endpoint routing due to DNS or config; gateway error pages.","commonSituations":"Corporate egress proxies or SSL-inspection appliances MITMing api.twilio.com; on-prem installs behind captive portals; DNS pointing api.twilio.com at a wrong host. Twilio's own API errors are JSON (and usually surface via the status check), so a decode failure almost always means transport-layer interference.","solutions":["Inspect the chained PhutilJSONParserException - it contains the actual response body; identify whether it is a proxy/captive-portal page or an empty body.","From the host running the daemons, verify you reach Twilio and get JSON: curl -sS https://api.twilio.com/2010-04-01/Accounts.json -u <SID>:<TOKEN>.","If a proxy or TLS inspection is mandatory, exempt api.twilio.com from interception.","Confirm the Twilio account SID / auth token in the mailer options so Twilio itself answers rather than an intermediate."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  $future = id(new PhabricatorTwilioFuture($sid, $token))->setMethod(...)->resolve();\n} catch (PhutilProxyException $ex) {\n  if (preg_match('/Expected JSON response/', $ex->getMessage())) {\n    // Body (in $ex->getPrevious()) is a proxy/error page: retry with backoff,\n    // and page the operator if it repeats - the transport is broken.\n    return retry_later();\n  }\n  throw $ex;\n}","preventionTips":["Exempt api.twilio.com from egress proxies and TLS inspection.","Smoke-test SMS delivery ('bin/mail send-test' with an SMS message) after network or mailer changes.","Treat repeated JSON-decode failures as a transport incident, not an application bug."],"tags":["phabricator","metamta","twilio","sms","json","api-response","proxy"],"backgroundTag":"malformed-api-response","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}