phacility/phabricator · error · PhutilProxyException
Expected JSON response from Twilio.
Error message
Expected JSON response from Twilio.
What it means
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.
Source
Thrown at src/applications/metamta/future/PhabricatorTwilioFuture.php:92
}
$this->future = $future;
}
return $this->future;
}
protected function didReceiveResult($result) {
list($status, $body, $headers) = $result;
if ($status->isError()) {
throw $status;
}
try {
$data = phutil_json_decode($body);
} catch (PhutilJSONParserException $ex) {
throw new PhutilProxyException(
pht('Expected JSON response from Twilio.'),
$ex);
}
return $data;
}
}
View on GitHub (pinned to 5720a38cfe)
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.
Defensive patterns
Strategy: retry
Try / catch
try {
$future = id(new PhabricatorTwilioFuture($sid, $token))->setMethod(...)->resolve();
} catch (PhutilProxyException $ex) {
if (preg_match('/Expected JSON response/', $ex->getMessage())) {
// Body (in $ex->getPrevious()) is a proxy/error page: retry with backoff,
// and page the operator if it repeats - the transport is broken.
return retry_later();
}
throw $ex;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to JSON decode response.
- Buildkite did not return a "%s"!
- Adapter ("%s") is configured for medium "%s", but this is no
- Configuration file is not properly formatted JSON. %s
- This contact number is already in use.
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/1039365968ea01e1.
Report an issue: GitHub.