로블록스 HttpService 요청이 안 될 때: 403·429·JSON 오류 점검 순서
HttpService 오류는 설정이 꺼진 경우, 요청 자체가 전송되지 않은 경우, 서버가 403·429로 거절한 경우, 응답은 왔지만 JSON 형식이 다른 경우가 서로 다릅니다. 한꺼번에 코드를 바꾸기보다 pcall 결과와 Response의 Success·StatusCode·Body를 따로 기록하면 실패 지점을 빠르게 좁힐 수 있습니다.
2026년 10월 1일 Windows 데스크톱 브라우저에서 Roblox Creator Hub의 In-game HTTP requests, HttpService API, Rate limits, Secrets stores 공개 문서와 Markdown 원문을 대조했습니다. HTTP 요청 활성화 경로, RequestAsync() 응답 필드, pcall(), Open Cloud 헤더·Secret 제한, 403·429 분류와 지수 백오프 권고를 확인했습니다. 실제 Roblox Studio 프로젝트나 외부 API 서버를 실행하지 않았으므로 특정 도메인의 응답, 계정 권한과 실제 요청 횟수는 측정하지 않았습니다. 아래 코드는 비밀 값을 포함하지 않는 진단용 뼈대이며 테스트 게임에서 자신의 엔드포인트로 검증해야 합니다.
1. ‘요청 실패’를 네 구간으로 먼저 나눕니다
제가 공식 예제와 API 문서를 비교하며 가장 먼저 구분한 것은 전송 전 오류, HTTP 오류 응답, 본문 해석 오류, 결과 적용 오류였습니다. 화면에는 모두 “불러오기 실패”로 보일 수 있지만 고쳐야 할 위치는 다릅니다.
| 구간 | 확인 신호 | 먼저 볼 곳 |
|---|---|---|
| 전송 전·전송 중 | pcall()이 false이고 Response 자체가 없음 | Allow HTTP Requests, URL, DNS·연결, 호출 한도 |
| HTTP 응답 | pcall()은 true지만 Success가 false | StatusCode, StatusMessage, 응답 헤더·Body |
| 본문 해석 | 2xx 응답 뒤 JSONDecode()에서 실패 | Content-Type, 빈 Body, HTML 오류 페이지, JSON 구조 |
| 게임 적용 | JSON도 읽었지만 UI·데이터가 바뀌지 않음 | 필드 이름·자료형, nil 검사, Client·Server 실행 위치 |
GetAsync()의 반환 문자열만 바로 JSONDecode()에 넣으면 앞의 두 구간이 한 오류처럼 보입니다. 처음 진단할 때는 상태 코드와 헤더까지 볼 수 있는 RequestAsync()로 바꿔 기록한 뒤 원인을 좁히는 편이 명확합니다.
2. Studio의 Allow HTTP Requests부터 확인합니다
Roblox 공식 문서는 GetAsync(), PostAsync(), RequestAsync()가 기본으로 활성화되지 않는다고 설명합니다. Studio에서 File → Experience Settings → Security → Allow HTTP Requests를 켜고 저장해야 합니다.
- 요청을 테스트할 정확한 게임을 Studio에서 엽니다. 비슷한 이름의 로컬 파일이나 다른 게임 설정을 바꾸지 않았는지 확인합니다.
- File → Experience Settings → Security로 이동해 Allow HTTP Requests를 켭니다.
- 설정을 저장하고 테스트를 완전히 종료한 뒤 다시 시작합니다.
- Server Script에서 한 번만 요청해 Output의 첫 오류를 기록합니다. 버튼 연타나 프레임 반복 호출은 멈춥니다.
3. pcall과 Response를 분리하는 진단 코드를 사용합니다
다음 뼈대는 전송 실패와 HTTP 상태 오류, JSON 해석 실패를 한 줄로 뭉개지 않습니다. URL은 자신이 관리하거나 사용 권한이 있는 HTTPS 테스트 엔드포인트로 바꾸고, API 키를 문자열로 넣지 마세요.
local HttpService = game:GetService("HttpService")
local function requestJson(url)
local sent, responseOrError = pcall(function()
return HttpService:RequestAsync({
Url = url,
Method = "GET",
Headers = {
["accept"] = "application/json",
},
})
end)
if not sent then
warn("요청을 전송하지 못함:", responseOrError)
return nil, "transport"
end
local response = responseOrError
print("HTTP 상태:", response.StatusCode, response.StatusMessage)
if not response.Success then
warn("서버가 오류 응답을 반환함:", response.StatusCode)
return nil, "http"
end
local decoded, dataOrError = pcall(function()
return HttpService:JSONDecode(response.Body)
end)
if not decoded then
warn("JSON 해석 실패:", dataOrError)
return nil, "json"
end
return dataOrError, nil
end
local data, failureStage = requestJson("https://example.com/health")
if data then
print("응답 필드 확인 완료")
else
warn("실패 구간:", failureStage)
end
운영 게임에서는 전체 Body에 토큰·개인정보가 들어 있을 수 있으므로 그대로 출력하지 않습니다. 상태 코드, 요청 종류, 실패 구간과 내부 추적용 식별자처럼 필요한 정보만 남기고 진단 로그도 일정 시간이 지나면 제거하세요.
4. 403과 429를 같은 재시도로 처리하지 않습니다
| 상태·현상 | 주요 의미 | 다음 확인 |
|---|---|---|
| 401 Unauthorized | 인증 정보가 없거나 유효하지 않음 | 키 이름·권한·만료·인증 방식 |
| 403 Forbidden | 서버가 요청을 이해했지만 허용하지 않음 | 키 권한, 대상 리소스, 허용 도메인·포트, 서버 정책 |
| 404 Not Found | 경로 또는 리소스를 찾지 못함 | URL 철자, ID, API 버전, 게시·삭제 상태 |
| 429 Too Many Requests | 호출 한도 또는 보호 한도에 도달 | retry-after·rate-limit 헤더, 호출 횟수와 반복 구조 |
| 500·503 | 대상 서비스의 내부 오류 또는 일시적 이용 불가 | 같은 입력의 시간대별 재현, 서비스 상태, 제한된 재시도 |
403은 기다린다고 자동 해결되는 상태가 아닐 수 있습니다. 특히 Open Cloud를 게임 서버에서 호출할 때 공식 문서는 x-api-key와 content-type 헤더만 허용하고, x-api-key 값은 Secrets store의 Secret이어야 한다고 안내합니다. URL 경로 매개변수에 ..가 들어가거나 제한된 포트를 사용해도 거절될 수 있습니다.
429는 즉시 연속 재시도하면 더 악화될 수 있습니다. 응답에 retry-after가 있으면 그 시간을 따르고, 없으면 2초·4초·8초처럼 간격을 늘립니다. 이용자가 버튼을 누를 때마다 요청하는 구조라면 서버 캐시, 중복 요청 합치기와 일괄 전송을 먼저 검토하세요.
5. 2xx 응답과 유효한 JSON은 별개의 조건입니다
상태 코드가 성공이어도 Body가 빈 문자열이거나 HTML, 일반 텍스트일 수 있습니다. 외부 서비스가 장애 페이지를 200으로 반환하거나, 엔드포인트가 JSON 대신 다른 형식을 쓰는 경우도 있습니다.
response.Success와StatusCode를 먼저 확인합니다.- 응답 헤더의 Content-Type이 기대한 JSON인지 봅니다. 헤더 이름의 대소문자에 기대는 코드는 피합니다.
- Body 전체를 공개 로그에 남기지 말고, 비어 있는지와 길이·앞부분의 안전한 형식만 개발 환경에서 확인합니다.
JSONDecode()도 별도의pcall()로 감싸 전송 오류와 구분합니다.- 디코딩 뒤 필요한 필드가 존재하는지, 문자열·숫자·배열 등 자료형이 맞는지 검증합니다.
예를 들어 data.items가 항상 배열이라고 가정하지 말고 typeof(data)와 필수 필드를 확인한 뒤 게임 상태에 반영합니다. 외부 응답은 신뢰할 수 없는 입력으로 취급하고 문자열 길이, 숫자 범위와 허용 값도 제한하세요.
6. API 키는 코드·로그가 아니라 Secrets store에서 읽습니다
API 키를 Script에 직접 넣으면 공동 편집, 버전 기록, 모델 복사와 로그를 통해 노출될 수 있습니다. Roblox의 게임별 Secrets store에 비밀을 저장하고 HttpService:GetSecret("이름")으로 가져옵니다. Open Cloud 예제도 x-api-key에 Secret 값을 전달합니다.
- Creator Hub에서 올바른 게임의 Secret을 만들었는지 확인합니다.
- 코드의 Secret 이름은 저장한 이름과 대소문자까지 일치시킵니다.
- 키 소유자와 대상 Universe·그룹·리소스 권한이 일치하는지 확인합니다.
- Secret이나 헤더 전체를
print()하지 않습니다. - 노출이 의심되면 코드에서 지우는 데 그치지 말고 발급처에서 키를 폐기·교체합니다.
Third-party API가 별도의 서명 방식이나 특정 헤더를 요구한다면 그 서비스의 공식 문서도 함께 확인해야 합니다. Open Cloud의 헤더 제한을 모든 외부 API의 규칙으로 확대 해석하지 마세요.
7. 재시도 전에 호출 구조와 한도를 줄입니다
이번 조사에서 특히 주의한 부분은 Roblox 공식 문서끼리도 한도 설명이 완전히 같지 않다는 점입니다. In-game HTTP requests 문서는 일반 외부 요청에 서버당 분당 500회 한도가 있고 Open Cloud 요청은 별도의 서버당 분당 2,500회 한도라고 설명합니다. 반면 Open Cloud Rate limits 문서는 게임 내 Open Cloud 호출도 서버당 분당 500회 고정 HTTP 한도에 기여한다고 적고 있습니다.
x-ratelimit-*·retry-after 헤더를 함께 확인하세요. 한도까지 사용하도록 설계하기보다 여유를 두는 편이 안전합니다.- 한 플레이어의 같은 요청을 짧은 시간 캐시할 수 있는지 확인합니다.
- 플레이어마다 한 번씩 보내는 대신 서버에서 모아 일괄 전송할 수 있는지 봅니다.
- Heartbeat·RenderStepped·무한 반복문 안에서 웹 요청을 보내지 않습니다.
- 429·503처럼 일시적일 수 있는 오류만 제한된 횟수로 재시도합니다.
- 401·403·404는 원인 수정 없이 반복하지 않습니다.
8. Studio 성공 뒤에는 공개 서버의 Observability를 봅니다
Studio에서 한 번 성공했다고 운영 서버에서도 충분한 것은 아닙니다. 실제 서버 수와 이용자가 늘면 호출량과 대상 API 지연이 달라집니다. Creator Hub의 Observability Dashboard에서 HttpService의 Request Count와 Response Time을 확인하고 요청 방법과 상태 코드별로 나눠 봅니다.
- 새 게시 버전을 소수 서버에 적용한 시각을 기록합니다.
- 기능을 한 번 실행해 성공 결과와 상태 코드를 확인합니다.
- 같은 시간대 Request Count가 예상보다 급증하지 않았는지 봅니다.
- 403·429·500·503 비율과 응답 시간 변화를 비교합니다.
- 오류가 늘면 재시도 횟수를 늘리기보다 마지막 정상 버전과 호출 경로 차이를 확인합니다.
공개 게임의 F9 Developer Console에서도 권한 있는 계정으로 Server Log와 Network를 함께 확인할 수 있습니다. 확인 순서는 공개 게임 F9 콘솔 확인법을 참고하고, 저장 문제라면 DataStore 저장 실패 점검법처럼 별도 서비스 설정을 분리하세요.
9. 수정 뒤 같은 입력으로 네 신호를 재검증합니다
| 확인 항목 | 완료 신호 | 다시 볼 지점 |
|---|---|---|
| 전송 | pcall() true | false면 설정·URL·연결·한도 |
| HTTP | Success true와 예상 상태 코드 | 403 권한, 429 호출량, 5xx 대상 서비스 |
| 본문 | JSON 해석과 필수 필드·자료형 검증 통과 | Content-Type, 빈 Body, HTML 오류 문서 |
| 운영 | 공개 서버 호출량·응답 시간·오류율 안정 | 중복 호출, 무제한 재시도, 서버 수 증가 |
핵심 순서는 Allow HTTP Requests 확인 → 한 번만 요청 → pcall과 Response 분리 → StatusCode·헤더 기록 → JSON 별도 검증 → 호출량 축소 → 공개 서버 관측입니다. 이 순서를 지키면 “HttpService가 안 된다”는 한 문장을 설정, 인증, 한도, 형식 또는 게임 로직 문제로 나눌 수 있습니다.
공식 출처·갱신일·적용 범위
아래 Roblox Creator Hub 1차 자료를 2026년 10월 1일 확인했습니다. 공개 Markdown 메타데이터에는 네 문서 모두 2026년 9월 30일 22:13 UTC 무렵 갱신으로 표시됐습니다.
- In-game HTTP requests — HTTP 요청 설정, Open Cloud 지원·제한, 403 가능 조건, pcall·지수 백오프·일괄 전송, Observability와 서버별 한도 설명을 확인했습니다.
- HttpService API reference —
RequestAsync(),GetAsync(),PostAsync(),JSONDecode(),GetSecret()의 반환과 예제를 확인했습니다. - Open Cloud Rate limits — endpoint별 한도,
x-ratelimit-*헤더, 429의retry-after와 지수 백오프, 게임 내 HttpService 한도 설명을 대조했습니다. - Secrets stores — 게임별 Secret 생성·사용 목적과 키를 코드에 직접 넣지 않는 방식을 확인했습니다.
적용 범위: Roblox Studio와 공개 게임의 서버 Script에서 제3자 웹 서비스 또는 HttpService 지원 Open Cloud 엔드포인트를 호출할 때의 첫 진단입니다. 특정 외부 서비스의 약관·인증 규격, 모든 Open Cloud 엔드포인트 권한, 실제 계정의 Secret 구성, 프록시·웹 서버 설정과 장애 여부는 판정하지 않습니다. 문서 간 한도 표기가 다른 부분은 더 낮은 기준으로 설명했으며, 실제 응답 헤더와 최신 공식 문서가 이 글보다 우선합니다.