{"record":{"id":"f4e26b8490d04480","repo":"Anankke/SSPanel-UIM","slug":"response-getbody-getcontents","errorCode":null,"errorMessage":"$response->getBody()->getContents()","messagePattern":"\\$response->getBody\\(\\)->getContents\\(\\)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Services/IM/Slack.php","lineNumber":47,"sourceCode":"        $url = 'https://slack.com/api/chat.postMessage';\n\n        $headers = [\n            'Authorization' => 'Bearer '.$this->token,\n            'Content-Type' => 'application/json',\n        ];\n\n        $body = [\n            'channel' => $to,\n            'text' => $msg,\n        ];\n\n        $response = $this->client->post($url, [\n            'headers' => $headers,\n            'json' => $body,\n        ]);\n\n        if ($response->getStatusCode() !== 200) {\n            throw new Exception($response->getBody()->getContents());\n        }\n    }\n}\n","sourceCodeStart":29,"sourceCodeEnd":51,"githubUrl":"https://github.com/Anankke/SSPanel-UIM/blob/d55a607191cfc51cdbc836fba85196ddef4df343/src/Services/IM/Slack.php#L29-L51","documentation":"Slack::send() throws a plain Exception whose message is the raw response body after POSTing to slack.com/api/chat.postMessage returned a status other than 200. Slack's Web API mostly answers 200 even for logical failures (with body {\"ok\":false,\"error\":...}), so this specific throw fires only on HTTP-level rejections: 401/403 for token problems, 404 for a wrong endpoint, or 429 when rate limited. That also means an invalid slack_token can silently pass this check when Slack answers 200 with ok:false — check the body, not just the status.","triggerScenarios":"401 invalid_auth / permission_denied: slack_token config empty, malformed, revoked, or a rotated token after reinstalling the app; 403 missing_scope: token lacks chat:write (or chat:writecustomize:write for custom sender names); 429 rate_limited: too many chat.postMessage calls, Slack answers 429 with a Retry-After header; 404: someone changed $url away from https://slack.com/api/chat.postMessage; channel_not_found / channel ID typos typically arrive as 200 + ok:false, escaping this check entirely.","commonSituations":"Legacy xoxb- bot token revoked when the Slack app was reinstalled or converted to granular permissions without the chat:write scope; slack_token left blank in panel config; heavy notification storms (mass user notifications) tripping Slack's tiered rate limits; sending to channels the bot was never invited to.","solutions":["Decode the exception message JSON: {\"ok\":false,\"error\":\"invalid_auth\"} points to the token; {\"error\":\"rate_limited\"} points to throttling.","For auth errors, verify the slack_token config value: OAuth token format (xoxb-...), still active, and issued by the same Slack app — reinstall the app if the token was regenerated.","Add the chat:write scope to the app's bot token (Slack app config -> OAuth & Permissions) and re-install so the scope takes effect.","On 429, honour the Retry-After response header and throttle chat.postMessage calls (e.g. queue notifications).","Because Slack returns 200 with ok:false for logical errors, also parse the response body and fail when ok is false — not only when the status differs from 200."],"exampleFix":"// before\n$response = $this->client->post($url, ['headers' => $headers, 'json' => $body]);\nif ($response->getStatusCode() !== 200) {\n    throw new Exception($response->getBody()->getContents());\n}\n\n// after\n$response = $this->client->post($url, ['headers' => $headers, 'json' => $body]);\n$payload = json_decode((string) $response->getBody(), true) ?: [];\nif ($response->getStatusCode() !== 200) {\n    throw new Exception(sprintf('Slack HTTP %d: %s', $response->getStatusCode(), $payload['error'] ?? 'unknown'));\n}\nif (($payload['ok'] ?? false) !== true) {\n    throw new Exception('Slack error: ' . ($payload['error'] ?? 'unknown'));\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: token shape and channel ID sanity before Slack::send()\n$token = trim((string) Config::obtain('slack_token'));\nif ($token === '' || !str_starts_with($token, 'xox')) {\n    throw new InvalidArgumentException('slack_token missing or not an xoxb-/xoxp- token');\n}\nif ($to <= 0 && !is_string($to)) {\n    throw new InvalidArgumentException('Slack channel must be an ID (C...) or name');\n}\n// Optional authoritative check (1 extra call): does the token work at all?\n$probe = $client->post('https://slack.com/api/auth.test', ['headers' => ['Authorization' => 'Bearer ' . $token]]);\nif ((json_decode((string) $probe->getBody(), true)['ok'] ?? false) !== true) {\n    throw new RuntimeException('Slack token rejected by auth.test');\n}","typeGuard":"function isSlackRateLimited(Exception $e): bool\n{\n    $json = json_decode($e->getMessage(), true);\n    return is_array($json) && (($json['error'] ?? null) === 'rate_limited' || ($json['ok'] ?? true) === false);\n}","tryCatchPattern":"try {\n    $slack->send($channelId, $text);\n} catch (GuzzleHttp\\Exception\\GuzzleException $e) {\n    $resp = $e instanceof GuzzleHttp\\Exception\\RequestException ? $e->getResponse() : null;\n    if ($resp !== null && $resp->getStatusCode() === 429) {\n        $wait = (int) $resp->getHeaderLine('Retry-After');\n        // schedule the notification again after $wait seconds instead of dropping it\n        return defer(fn () => $slack->send($channelId, $text), $wait);\n    }\n    throw $e;\n} catch (Exception $e) {\n    $payload = json_decode($e->getMessage(), true);\n    $log->warning('Slack delivery failed', ['error' => $payload['error'] ?? $e->getMessage()]);\n}","preventionTips":["Remember Slack returns 200 with {\"ok\":false} for logical errors (invalid_auth, channel_not_found) — also inspect the response body, not just status codes.","Confirm the app token has chat:write scope and the bot is a member of the target channel before wiring notifications.","Honour the Retry-After header on 429s; queue bursts rather than posting in tight loops.","Run auth.test once at startup to detect revoked tokens early with a clear error."],"tags":["php","slack","guzzle","api-error","authentication","rate-limit","notifications"],"backgroundTag":"slack-api-error","analyzedSha":"d55a607191cfc51cdbc836fba85196ddef4df343","analyzedAt":"2026-08-21T04:59:05.849Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}