로블록스 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가 falseStatusCode, 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를 켜고 저장해야 합니다.

  1. 요청을 테스트할 정확한 게임을 Studio에서 엽니다. 비슷한 이름의 로컬 파일이나 다른 게임 설정을 바꾸지 않았는지 확인합니다.
  2. File → Experience Settings → Security로 이동해 Allow HTTP Requests를 켭니다.
  3. 설정을 저장하고 테스트를 완전히 종료한 뒤 다시 시작합니다.
  4. Server Script에서 한 번만 요청해 Output의 첫 오류를 기록합니다. 버튼 연타나 프레임 반복 호출은 멈춥니다.
혼동하기 쉬운 설정DataStore를 Studio에서 시험할 때 쓰는 Enable Studio Access to API Services와 외부 웹 요청의 Allow HTTP Requests는 같은 토글이 아닙니다. DataStore 글을 보고 앞의 설정만 켰다면 HttpService 설정을 다시 확인하세요.

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 대신 다른 형식을 쓰는 경우도 있습니다.

  1. response.Success와 StatusCode를 먼저 확인합니다.
  2. 응답 헤더의 Content-Type이 기대한 JSON인지 봅니다. 헤더 이름의 대소문자에 기대는 코드는 피합니다.
  3. Body 전체를 공개 로그에 남기지 말고, 비어 있는지와 길이·앞부분의 안전한 형식만 개발 환경에서 확인합니다.
  4. JSONDecode()도 별도의 pcall()로 감싸 전송 오류와 구분합니다.
  5. 디코딩 뒤 필요한 필드가 존재하는지, 문자열·숫자·배열 등 자료형이 맞는지 검증합니다.

예를 들어 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 한도에 기여한다고 적고 있습니다.

보수적인 판단 기준두 공개 문서의 표기가 다른 동안에는 더 낮은 서버당 분당 500회를 전체 안전 예산으로 잡고, 각 엔드포인트 문서와 실제 응답의 x-ratelimit-*·retry-after 헤더를 함께 확인하세요. 한도까지 사용하도록 설계하기보다 여유를 두는 편이 안전합니다.
  1. 한 플레이어의 같은 요청을 짧은 시간 캐시할 수 있는지 확인합니다.
  2. 플레이어마다 한 번씩 보내는 대신 서버에서 모아 일괄 전송할 수 있는지 봅니다.
  3. Heartbeat·RenderStepped·무한 반복문 안에서 웹 요청을 보내지 않습니다.
  4. 429·503처럼 일시적일 수 있는 오류만 제한된 횟수로 재시도합니다.
  5. 401·403·404는 원인 수정 없이 반복하지 않습니다.

8. Studio 성공 뒤에는 공개 서버의 Observability를 봅니다

Studio에서 한 번 성공했다고 운영 서버에서도 충분한 것은 아닙니다. 실제 서버 수와 이용자가 늘면 호출량과 대상 API 지연이 달라집니다. Creator Hub의 Observability Dashboard에서 HttpService의 Request Count와 Response Time을 확인하고 요청 방법과 상태 코드별로 나눠 봅니다.

  1. 새 게시 버전을 소수 서버에 적용한 시각을 기록합니다.
  2. 기능을 한 번 실행해 성공 결과와 상태 코드를 확인합니다.
  3. 같은 시간대 Request Count가 예상보다 급증하지 않았는지 봅니다.
  4. 403·429·500·503 비율과 응답 시간 변화를 비교합니다.
  5. 오류가 늘면 재시도 횟수를 늘리기보다 마지막 정상 버전과 호출 경로 차이를 확인합니다.

공개 게임의 F9 Developer Console에서도 권한 있는 계정으로 Server Log와 Network를 함께 확인할 수 있습니다. 확인 순서는 공개 게임 F9 콘솔 확인법을 참고하고, 저장 문제라면 DataStore 저장 실패 점검법처럼 별도 서비스 설정을 분리하세요.

9. 수정 뒤 같은 입력으로 네 신호를 재검증합니다

확인 항목완료 신호다시 볼 지점
전송pcall() truefalse면 설정·URL·연결·한도
HTTPSuccess 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 구성, 프록시·웹 서버 설정과 장애 여부는 판정하지 않습니다. 문서 간 한도 표기가 다른 부분은 더 낮은 기준으로 설명했으며, 실제 응답 헤더와 최신 공식 문서가 이 글보다 우선합니다.

이 글은 비공식 가이드입니다. 직접 실행한 범위와 공식 문서로만 확인한 범위를 구분해 읽고, 계정·결제·연령 관련 최종 기준은 Roblox 공식 안내와 보호자 판단을 우선하세요.
이 글의 순서