Case study

네이버 커머스 API

backend·9 commits·2026

Architecture

pinch to zoom · drag to pan

Stack

  • Python 3.12
  • FastAPI
  • httpx (async)
  • bcrypt
  • Pydantic v2
  • Gunicorn

External integrations

  • Naver Commerce API (OAuth2)
  • Cloud Run

Highlights

  • bcrypt-signed OAuth2 (Naver-spec)
  • 85min token cache
  • Korean order status translator

문제정의

스마트스토어 챗봇(naver-smartstore)이 의도 분류와 응답 생성에 집중하려면, 네이버 커머스 API의 인증·페이지네이션·상태 코드 같은 사정은 챗봇 코드 바깥에 있어야 했다. 그런데 네이버 커머스 API의 OAuth2는 표준 client_secret 모델이 아니다 — bcrypt 해시 + base64 인코딩으로 직접 서명을 생성해 token endpoint에 보내야 한다.

사정챗봇이 직접 다룰 때의 비용
bcrypt 서명 알고리즘LangGraph 노드마다 서명 재계산 → 코드 복제 + 키 노출 위험
토큰 만료호출마다 토큰 발급 → 호출 비용·레이트리밋 압박
응답 페이로드 한글 상태 코드DELIVERED·PAYMENT_WAITING 같은 영문 상태를 LLM 프롬프트로 그대로 노출 → 사용자 응답 일관성 저하
에러 형식Naver의 비표준 에러 응답을 챗봇 노드마다 파싱 → 분산된 핸들링
pinch to zoom · drag to pan

목표는 세 가지로 좁혔다.

  1. 인증을 한 곳에 격리 — bcrypt 서명·토큰 발급·캐싱을 챗봇 바깥의 단일 컴포넌트로.
  2. 상품/주문 조회를 LLM-친화 형식으로 정규화 — 영문 상태 코드·페이로드 차이를 챗봇 입력 전 흡수.
  3. 운영 가능한 컨테이너 산출물 — Cloud Run + Gunicorn 워커, 토큰 발급 실패 시에도 health endpoint가 살아 있도록.

구현

bcrypt 서명 + 토큰 캐시 매니저

핵심은 CommerceTokenManager 단일 클래스. 서명 생성·토큰 발급·만료 추적·재발급을 한 객체 안에 가두고, 라우터에서는 await token_manager.get_token() 한 호출로 받는다.

책임구현
서명 생성bcrypt.hashpw(password, client_secret)base64.urlsafe_b64encode (Naver 스펙)
토큰 발급POST /v1/oauth2/token with client_credentials + signature
캐시메모리 dict + expires_at (발급 시각 + 85m, 실제 만료보다 보수적으로)
재발급expires_at 도래 또는 401 응답 수신 시 자동 재발급
상태 노출GET /api/auth/status로 캐시 잔여 시간·토큰 prefix 노출 (운영 디버그용)
pinch to zoom · drag to pan

85분(공식 만료 90분에서 5분 마진)으로 잡은 이유는 두 가지 — 만료 직전 호출이 401로 떨어지는 레이스 컨디션 회피, 그리고 워커가 여러 개일 때 캐시 만료 직후 동시 재발급이 몰리는 thundering-herd 완화.

상품·주문 조회 서비스 (async httpx)

ProductServiceOrderService는 동일한 패턴 — 토큰 매니저에서 Bearer 받고, httpx.AsyncClient로 호출하고, 결과를 정규화. 차이는 정규화 깊이다.

  • ProductService — 검색(/v1/products/search) + 채널 상품 상세(/v2/products/channel-products/{id})
  • OrderService — 주문 검색(/v1/pay-order/seller/product-orders) + 상세 + 한글 상태 번역

주문 검색은 공식 필터 스펙(startSearchDate/endSearchDate/productOrderStatuses CSV 등)에 맞췄다. 04-03에 한 번 내부 변경한 시그니처를 공식 필터에 다시 정렬한 게 이 라운드의 핵심 정리 작업이었다 — 챗봇이 "Naver의 페이로드 형태"를 그대로 의식하지 않게 하기 위함.

한글 주문 상태 번역기

영문 상태 코드를 LLM 프롬프트에 그대로 노출하면, 모델이 PAYMENT_WAITING을 "결제 대기"로 의역할 때마다 표현이 흔들린다. 챗봇이 안정적으로 답하려면 백엔드가 한 가지 한국어 표현으로 고정해서 내려줘야 한다.

영문 상태한글 (고정)
PAYMENT_WAITING결제 대기
PAYED결제 완료
DELIVERING배송 중
DELIVERED배송 완료
PURCHASE_DECIDED구매 확정
CANCELED / RETURNED / EXCHANGED취소 / 반품 / 교환

번역은 _normalize_order_item에서 일괄 처리 — 챗봇 노드는 영문 상태를 볼 일이 없다.

운영 가능한 컨테이너 (Cloud Run + Gunicorn)

FastAPI 단일 프로세스만 띄우면 콜드스타트 후 첫 요청이 토큰 발급에 묶여 느려진다. Gunicorn으로 워커를 띄우고, 워커 단위로 토큰 캐시를 공유(메모리 dict)하는 방식 — 워커가 늘어나도 외부 토큰 호출은 한 번에 한 번씩만 추가된다.

배포는 04-08 단일 커밋으로 GitHub Actions → Cloud Run 워크플로우를 한 번에 박았다. gunicorn.conf.py가 워커 수·바인드·로그 포맷을 한 곳에서 관리한다.

출시

빠른 스캐폴드 + 운영 시그니처 정리 패턴.

시기주요 변경
Day 1 (2026-03-30)bcrypt 인증 + 토큰 매니저 · 상품/주문 엔드포인트 (smartstore와 같은 날 푸시)
Week 1 (~04-03)인증 재시도 정리 · 공식 주문 조회 필터로 시그니처 재정렬 · PRD/Postman 검증 자산 갱신
Week 2 (~04-07)상품 질문 생성 스크립트 추가 (smartstore 벤치마크 자산 연계)
Week 2 (04-08)Cloud Run 배포 워크플로우 · gunicorn.conf.py · uv.lock 트래킹

특이점: 스캐폴드 첫날(2026-03-30)이 naver-smartstore의 Day 1과 같다. 두 저장소를 한 작업 세션에서 같이 푸시한 셈 — 챗봇이 호출할 표면이 명확했기에 가능했다.

결과학습

항목결과
안정화 기간10일 (Day 1 스캐폴드 → 04-08 prod 배포)
토큰 호출 절감매 호출 발급 → 85분당 1회 (워커당)
인증 코드 격리bcrypt 서명·재발급·캐시가 CommerceTokenManager 단일 객체에 캡슐화
한글 상태 일관성영문 상태 코드 0건 LLM 입력 (모든 노출 채널에서 고정 한국어)
운영 산출물Containerfile · gunicorn.conf.py · GitHub Actions → Cloud Run · /api/auth/status

기술·운영적으로 가져간 학습은 네 가지다.

  1. 챗봇 옆에 두는 API 래퍼는 "외부 SDK의 quirk를 흡수하는 막"이다. bcrypt 서명·토큰 만료·영문 상태 코드 같은 외부 사정을 챗봇이 의식하지 않게 하는 게 래퍼의 본업. 래퍼가 두꺼워질수록 챗봇 노드가 얇아진다.
  2. 토큰 만료 마진은 단순 안전이 아니라 thundering-herd 완화 장치. 공식 90분에 85분으로 잡으면, 만료 시각 직후 동시 재발급 폭주가 분산된다. 5분 마진은 비용이 아니라 보험.
  3. 공식 스펙과 내부 시그니처는 일찍 정렬해야 한다. Day 1 직후(04-03)에 주문 조회 시그니처를 공식 필터에 다시 맞춘 작업이 가장 ROI 높은 정리였다. 늦게 정렬할수록 챗봇이 잘못된 추상화에 묶인다.
  4. 상태 번역은 백엔드의 책임. LLM에 영문 상태를 의역하게 두면 표현 안정성을 잃는다. 번역 테이블은 작아도 백엔드에 두는 게 정답.
pinch to zoom · drag to pan

Next

  • 워커 간 토큰 캐시 공유 — 현재는 워커별 메모리 dict. Redis 같은 외부 캐시로 옮기면 워커 수와 무관하게 토큰 호출이 한 번으로 수렴.
  • 레이트리밋·서킷 브레이커 도입 — Naver 측 일시 장애나 레이트리밋 응답을 챗봇 노드까지 새지 않게, select-hotel에서 검증한 5회/30초 패턴을 이식.
  • /api/auth/status 확장 → 운영 대시보드 — 토큰 잔여 시간·재발급 횟수·최근 401 발생을 메트릭으로 노출. 비정상 인증 사이클을 사후가 아니라 실시간으로 감지.