Stripe API: API 설계자가 배워야 할 모범 사례

Yukio Ikeda

Yukio Ikeda

28 February 2026

Stripe API: API 설계자가 배워야 할 모범 사례

Apidog 엔터프라이즈

온프레미스 배포

SSO & RBAC

SOC 2 준수

Apidog Enterprise 살펴보기

Stripe를 개발자들 사이에서 가장 사랑받는 API로 만든 아키텍처 결정에 대한 심층 분석.


개발자들이 "훌륭한 API 디자인"에 대해 이야기할 때, Stripe는 거의 항상 가장 먼저 언급되는 이름입니다. 99%의 개발자 만족도와 업계 평균보다 3배 더 높은 개발자 전환율을 자랑하는 Stripe는 단순히 결제 API를 만든 것이 아니라, 현대적인 API 디자인의 표준을 제시했습니다.

하지만 Stripe의 API는 정확히 무엇이 그렇게 훌륭하게 만들었을까요? 마법일까요? 운일까요? 아니면 천재 엔지니어 팀의 작품일까요?

실제로는 어떤 API 팀이라도 채택할 수 있는 신중하고 반복 가능한 디자인 패턴들의 집합입니다. 자세히 살펴보겠습니다.

철학: API는 개발자를 위한 제품이다

세부 사항에 뛰어들기 전에, Stripe의 핵심 철학을 이해해야 합니다: API는 제품이고, 개발자는 고객입니다.

이것은 단순히 마케팅 용어가 아닙니다. Stripe는 모든 새로운 엔드포인트가 따라야 하는 20페이지 분량의 내부 API 디자인 문서를 유지하는 것으로 알려져 있습니다. 그들은 API 변경에 대한 교차 기능 검토 팀을 가지고 있습니다. 심지어 문서 품질을 엔지니어링 경력 경로에 통합했습니다.

그 결과는 무엇일까요? API의 한 부분을 이해하면 다른 모든 부분이 직관적으로 느껴지는 API입니다.

패턴 1: 사람이 읽을 수 있는 객체 ID

대부분의 API는 550e8400-e29b-41d4-a716-446655440000와 같은 UUID를 사용합니다. Stripe는 더 영리한 일을 합니다:

ch_3MqZlPLkdIwHu7ix0slN3S9y    # 청구(Charge)
cus_NffrFeUfNV2Hib              # 고객(Customer)
pi_3MtwBwLkdIwHu7ix28aiHDKq     # 결제 의도(PaymentIntent)
sub_1MowQVLkdIwHu7ixeRlqHVzs    # 구독(Subscription)

구조:

왜 중요할까요:

즉각적인 디버깅: 로그에서 ch_를 보면 즉시 청구(charge)라는 것을 알 수 있습니다. 추가적인 맥락이 필요 없습니다.

오류 방지: 청구 ID가 필요한 곳에 실수로 고객 ID를 전달했나요? 접두사 불일치로 버그를 쉽게 파악할 수 있습니다.

API 효율성: Stripe는 ID에서 객체 유형을 추론하여 추가 매개변수 없이 다형성 조회를 가능하게 합니다.

보안: 순차적 ID(user_1, user_2...)와 달리, 이 ID는 비즈니스 규모나 고객 수에 대한 어떤 정보도 노출하지 않습니다.

이 패턴은 매우 효과적이어서 Clerk 및 Linear와 같은 회사들이 이를 채택했습니다. 당신도 그래야 합니다.

패턴 2: 날짜 기반 버전 관리 (v1, v2, v3가 아님)

기존 API 버전 관리는 v2를 출시할 때 클라이언트를 망가뜨립니다. Stripe의 접근 방식은 근본적으로 다릅니다:

Stripe-Version: 2024-10-28

작동 방식:

첫 API 요청을 하면, 당신의 계정은 그 날짜의 API 버전에 "고정"됩니다.

호환성을 깨뜨리는 변경 사항은 명시적으로 업그레이드하지 않는 한 통합에 영향을 주지 않습니다.

Stripe-Version 헤더를 설정하여 요청별로 새 버전을 테스트할 수 있습니다.

내부적으로 역방향 호환성 계층은 요청/응답을 당신이 고정한 버전에 맞게 변환합니다.

그 천재성: Stripe는 API를 지속적으로 발전시키면서도 7년 된 통합이 계속 작동하도록 할 수 있습니다. 강제 마이그레이션도 없고, 버전 서비스 종료 발표도 없고, 화난 개발자도 없습니다.

구현 팁: API를 유지 관리한다면 이 패턴을 고려해 보세요. 내부 변환 계층을 구축해야 하지만, 이로 인해 얻는 개발자 신뢰는 모든 엔지니어링 시간을 투자할 가치가 있습니다.

패턴 3: 확장 가능한 객체

다음은 흔한 API 안티 패턴입니다:

// 첫 번째 요청: 주문 가져오기
GET /orders/123
{
  "id": "ord_123",
  "customer_id": "cus_456",
  "product_ids": ["prod_789", "prod_012"]
}

// 두 번째 요청: 고객 가져오기
GET /customers/456
// 세 번째 요청: 제품 가져오기...

세 번의 왕복 통신. Stripe는 이를 우아하게 해결합니다:

GET /v1/checkout/sessions/cs_123?expand[]=customer&expand[]=line_items
{
  "id": "cs_123",
  "customer": {
    "id": "cus_456",
    "email": "user@example.com",
    "name": "Jane Doe"
    // 전체 고객 객체가 임베딩됨
  },
  "line_items": {
    "data": [...]
    // 전체 품목 객체가 임베딩됨
  }
}

주요 기능:

한 번의 요청으로 모든 데이터. 이 패턴 하나만으로도 API 호출을 50% 이상 줄일 수 있습니다.

패턴 4: 올바른 커서 기반 페이지네이션

오프셋 페이지네이션 (?page=2&limit=10)은 요청 사이에 데이터가 변경될 때 문제가 발생합니다. Stripe는 커서 기반 페이지네이션을 사용합니다:

GET /v1/charges?limit=10

응답:

{
  "data": [...],
  "has_more": true,
  "url": "/v1/charges"
}

다음 페이지:

GET /v1/charges?limit=10&starting_after=ch_last_id_from_previous_page

커서가 승리하는 이유:

  1. 일관성: 새 레코드가 추가되더라도 항목이 건너뛰거나 중복되지 않습니다.
  2. 성능: 데이터베이스에서 오프셋을 계산할 필요가 없습니다.
  3. 단순성: 마지막으로 받은 ID만 전달하면 됩니다.

보너스: Stripe의 SDK에는 이를 투명하게 처리하는 자동 페이지네이션 도우미가 포함되어 있습니다.

패턴 5: 멱등성 키

분산 시스템에서는 네트워크가 실패합니다. 요청 시간 초과가 발생합니다. 클라이언트는 재시도합니다. 멱등성이 없으면 고객에게 두 번 청구할 수 있습니다.

Stripe의 해결책:

POST /v1/charges
Idempotency-Key: ord_123_attempt_1

보장: 동일한 멱등성 키를 두 번 보내면, Stripe는 첫 번째 요청의 결과를 반환합니다. 중복 청구는 절대 없습니다.

최고의 실천 방법:

이것은 단순한 기능이 아니라, 돈, 재고 또는 "한 번만 실행"해야 하는 모든 작업을 처리하는 모든 API의 근본적인 설계 원칙입니다.

패턴 6: 일관된 응답 구조

모든 Stripe 리소스는 동일한 형태를 따릅니다:

{
  "id": "ch_xxx",
  "object": "charge",
  "created": 1677123456,
  "livemode": false,
  "metadata": {},
  ...
}

항상 존재하는 필드:

왜 중요할까요: 하나의 Stripe 리소스로 작업해 본 경험이 있다면, 모든 리소스가 어떻게 작동하는지 알 수 있습니다. 인지 부하 감소 = 더 행복한 개발자.

패턴 7: 실행 가능한 오류 응답

대부분의 API는 다음과 같은 오류를 반환합니다:

{
  "error": "invalid_request"
}

Stripe는 한 단계 더 나아갑니다:

{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "decline_code": "insufficient_funds",
    "message": "잔액이 부족합니다.",
    "param": "source",
    "doc_url": "https://stripe.com/docs/error-codes/card-declined",
    "request_log_url": "https://dashboard.stripe.com/logs/req_xxx"
  }
}

얻을 수 있는 것:

  1. 유형 + 코드: 프로그램적인 오류 처리
  2. 거부 코드: 특정 이유 (카드 오류의 경우)
  3. 사람이 읽을 수 있는 메시지: 사용자에게 보여주기 안전함 (카드 오류의 경우)
  4. 매개변수: 어떤 필드가 문제를 일으켰는지
  5. 문서 URL: 문제 해결 문서로의 직접 링크
  6. 요청 로그 URL: 한 번의 클릭으로 대시보드 디버깅

이것은 개발자의 시간을 존중하는 오류 처리입니다.

패턴 8: 확장성을 위한 메타데이터

모든 주요 Stripe 객체는 metadata, 즉 사용자 지정 키-값 저장을 지원합니다:

{
  "id": "cus_123",
  "metadata": {
    "internal_user_id": "usr_abc",
    "plan_tier": "enterprise",
    "sales_rep": "jane@company.com"
  }
}

제한: 키 50개, 키 이름 40자, 값 500자.

사용 사례:

이 패턴은 Stripe가 모든 사용 사례를 예측할 수 없다는 진실을 인정합니다. 그래서 그들은 구조화된 비상구를 제공합니다.

패턴 9: 3열 문서화

Stripe의 문서 레이아웃은 수없이 복사되었습니다:

탐색 내용 코드
제품 영역 설명, 튜토리얼 실시간, 실행 가능한 예제

그 마법:

하지만 진짜 비밀은 이것입니다: Stripe는 문서를 부차적인 것이 아니라 제품으로 취급합니다. 그들은 엔지니어를 위한 글쓰기 수업을 운영합니다. 문서 품질은 승진에 영향을 미칩니다. 그들은 맞춤형 문서 프레임워크(Markdoc)를 구축했습니다.

패턴 10: 일등 시민으로서의 테스트 모드

Stripe는 단순히 테스트 키만 있는 것이 아니라, 테스트 모드는 병렬 우주와 같습니다:

sk_test_xxx  → 테스트 모드 비밀 키
sk_live_xxx  → 라이브 모드 비밀 키

테스트 모드 기능:

철학: 개발자들은 두려움 없이 탐색하고, 실험하고, 오류를 일으킬 수 있어야 합니다. 테스트 모드는 학습 곡선에서 마찰을 제거합니다.


마무리: 오늘 적용할 수 있는 것

이러한 패턴을 사용하기 위해 결제 API를 구축할 필요는 없습니다:

ID에 접두사를 붙이세요usr_, ord_, inv_... 비용이 들지 않으며 모두에게 도움이 됩니다.

멱등성을 고려하여 설계하세요 → 특히 상태 변경 작업의 경우.

커서 페이지네이션을 사용하세요 → 오프셋은 함정입니다.

오류를 실행 가능하게 만드세요 → 문서 링크, 요청 ID, 특정 코드를 포함하세요.

메타데이터 필드를 추가하세요 → 예측할 수 없는 사용 사례에 대비하여 API를 미래에도 유효하게 만드세요.

문서에 투자하세요 → 개발자들이 받는 첫인상(때로는 유일한 인상)입니다.

Stripe의 API가 우연히 골드 스탠다드가 된 것은 아닙니다. API 디자인을 규율로, 문서를 제품으로, 개발자를 즐겁게 할 가치가 있는 고객으로 대우한 결과입니다.

모든 패턴이 여기에 있습니다. 이제 가서 가져다 쓰세요.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요

Stripe API: API 설계자가 배워야 할 모범 사례