RMT.GG/판매자 개발자 문서
v1

판매자 API

리스트 자동화, 판매 이행 및 주문 이벤트 스트리밍. 결제 후 온디맨드 재고 보충을 위한 아웃바운드 웹훅 및 예약 엔드포인트 포함.

REST 오픈 API

제안 및 주문을 위한 Bearer 인증 /api/v1, 발견 및 속도 제한 헤더 포함.

아웃바운드 웹훅

주문 및 제안 생애 주기 이벤트를 위한 서명된 HTTPS(또는 Discord) 전송.

예약 / 보충

로컬 재고가 부족할 때 결제 후 서버에서 COMPLEX 재고를 발행.

당신이 만들 수 있는 것

판매자 오픈 API는 Discord 알림, 재고 동기화, Zapier 스타일 자동화 또는 RMT.GG 위에 맞춤형 백오피스를 원하는 판매자를 위한 것입니다.

  • 제안 관리
    초안 생성, 안전한 필드 업데이트, 게시 및 /api/v1/offers를 통해 보관.
  • 판매 이행
    판매자 주문 목록 및 검사 후 선택적 증거 URL로 배송 완료로 표시.
  • 제한 내에서 유지
    모든 키는 분당 300 요청으로 제한됩니다. 응답에는 X-RateLimit-* 헤더가 포함됩니다.
  • 실시간으로 반응
    주문 및 제안 이벤트를 구독하거나 예약 웹훅으로 COMPLEX 재고를 보충.
  • 상점에서 결제 받기
    승인된 파트너는 외부 상점에서 구매자를 호스팅된 체크아웃으로 보낸 후, 주문이 결제되면 이행할 수 있습니다.

빠른 시작

개발자 설정에서 API 키를 생성한 후, 디스커버리를 호출하여 실시간 카탈로그를 출력하세요.

  1. 1설정 열기 → 개발자 (별도의 활성화 단계 없음).
  2. 2API 키를 생성하고 비밀을 한 번 복사합니다(rmt_sk_live_…). 비밀 관리자에 저장합니다.
  3. 3GET /api/v1을 호출하여 Authorization: Bearer로 범위, 할당량 및 작업을 확인합니다.
GET/api/v1

발견 문서

범위, 할당량, 웹훅 이벤트 및 전체 작업 카탈로그를 반환합니다. 유효한 API 키는 모두 작동합니다.

인증

모든 /api/v1 요청에 대해 라이브 비밀 키를 전송합니다. HTTPS만 선호합니다. 공개 클라이언트나 브라우저 번들에 키를 포함하지 마십시오.

선호하는 헤더

http
Authorization: Bearer rmt_sk_live_<prefix>_<secret>

대체 헤더

http
X-Api-Key: rmt_sk_live_<prefix>_<secret>

재사용 가능한 타입스크립트 클라이언트 (Bearer 인증, 타입 오류, 429 재시도)

typescript
const API_BASE = "https://rmt.gg/api/v1";
const API_KEY = process.env.RMT_API_KEY!; // rmt_sk_live_…

export class RmtApiError extends Error {
  constructor(
    readonly status: number,
    readonly code: string | undefined,
    message: string,
    readonly retryAfterSec?: number,
  ) {
    super(message);
    this.name = "RmtApiError";
  }
}

type RmtFetchInit = RequestInit & { idempotencyKey?: string };

export async function rmtFetch<T>(path: string, init: RmtFetchInit = {}): Promise<T> {
  const headers = new Headers(init.headers);
  headers.set("Authorization", `Bearer ${API_KEY}`);
  // Alternate: headers.set("X-Api-Key", API_KEY);
  headers.set("Accept", "application/json");
  if (init.body && !headers.has("Content-Type")) {
    headers.set("Content-Type", "application/json");
  }
  if (init.idempotencyKey) headers.set("Idempotency-Key", init.idempotencyKey);

  const res = await fetch(`${API_BASE}${path}`, { ...init, headers });
  const retryAfter = Number(res.headers.get("Retry-After") ?? "");
  const body = (await res.json().catch(() => ({}))) as {
    error?: string;
    code?: string;
    retryAfter?: number;
  };

  if (res.status === 429) {
    throw new RmtApiError(
      429,
      body.code ?? "RATE_LIMITED",
      body.error ?? "Rate limited",
      Number.isFinite(retryAfter) ? retryAfter : body.retryAfter,
    );
  }
  if (!res.ok) {
    throw new RmtApiError(res.status, body.code, body.error ?? res.statusText);
  }
  return body as T;
}

export async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
  let attempt = 0;
  for (;;) {
    try {
      return await fn();
    } catch (err) {
      attempt += 1;
      if (!(err instanceof RmtApiError) || err.status !== 429 || attempt >= maxAttempts) {
        throw err;
      }
      const waitSec = Math.max(1, err.retryAfterSec ?? 1);
      await new Promise((r) => setTimeout(r, waitSec * 1000));
    }
  }
}

유출 시 회전

키가 유출되면 개발자 설정에서 이를 취소하고 새 키를 생성합니다. 라이브 중이라면 취소하기 전에 자동화를 업데이트하십시오.

범위

각 API 키는 엔드포인트를 제한하는 범위를 가집니다. 범위가 누락되면 403 SCOPE_MISSING이 반환됩니다.

offers:read
offers:write
orders:read
orders:write
webhooks:manage
checkout:write
  • offers:read: 당신의 제안을 나열하고 가져옵니다.
  • offers:write: 제안을 생성, 업데이트, 게시 및 삭제합니다.
  • orders:read: 판매자 주문을 나열하고 가져옵니다.
  • orders:write: 주문을 배송 완료로 표시합니다.
  • webhooks:manage: 향후 Open API 웹훅 관리를 위해 예약되었습니다. 오늘 알림에서 Discord/Telegram을 설정하고 개발자 설정에서 JSON 웹훅을 구성하세요.
  • checkout:write: 호스팅 결제 세션을 생성하고 읽습니다. 관리자가 승인한 파트너 결제가 필요합니다.

기본 키 범위

새 키는 offers:read, offers:write, orders:read 및 orders:write를 받습니다. 아웃바운드 웹훅 CRUD는 설정 UI(세션 인증)에 남아 있습니다.

제안 API

제안 식별자는 공개 URL 슬러그 또는 숫자 ID를 수용합니다. 응답에는 내부 ID 및 sellerId가 생략됩니다.

PATCH로 변경할 수 없는 것

재고 행, 옵션 가격, 미디어 및 속성은 판매자 편집기(또는 미래의 엔드포인트)에서 관리되며, 오늘은 PATCH를 통해 변경할 수 없습니다.

GET/api/v1/offers
offers:read

당신의 제안 목록

archive=active(기본값), archived 또는 all로 필터링합니다.

요청

  • archive
    query
    유형
    string
    필수
    선택 사항
    설명
    One of "active" (default), "archived", or "all".
  • Response: { offers: Offer[], total: number }. Numeric id and sellerId are omitted.
POST/api/v1/offers
offers:write

초안 제안 생성

인증된 판매자가 소유한 빈 초안을 생성합니다. 본문은 필요하지 않습니다.

  • No request body required.
  • Response 201: { offer: Offer }.
GET/api/v1/offers/:urlOrId
offers:read

하나의 제안 가져오기

공식 URL 슬러그 또는 숫자 ID로 로드합니다. 관계(옵션)가 포함될 수 있으며, 재고 항목은 포함되지 않습니다.

요청

  • urlOrId
    path
    유형
    string
    필수
    필수
    설명
    Offer.url slug or Offer.id.
  • Returns relations (options, etc.) when available; items are not included.
PATCH/api/v1/offers/:urlOrId
offers:write

제안 필드 업데이트

안전한 하위 집합의 목록 필드를 패치합니다. 아웃바운드 웹훅이 구성되면 offer.updated를 방출합니다.

요청

  • urlOrId
    path
    유형
    string
    필수
    필수
    설명
    Offer.url slug or Offer.id.
  • title
    body
    유형
    string
    필수
    선택 사항
    설명
    Listing title.
  • description
    body
    유형
    string
    필수
    선택 사항
    설명
    Listing description.
  • visibility
    body
    유형
    string
    필수
    선택 사항
    설명
    PUBLIC | PRIVATE | UNPUBLISHED.
  • categoryId
    body
    유형
    number
    필수
    선택 사항
    설명
    Catalog category id.
  • offeringId
    body
    유형
    number
    필수
    선택 사항
    설명
    Catalog offering id.
  • thumbnail
    body
    유형
    string
    필수
    선택 사항
    설명
    Thumbnail URL or asset reference.
  • offerType
    body
    유형
    string
    필수
    선택 사항
    설명
    Offer type string used by the listing.
  • listingMode
    body
    유형
    string
    필수
    선택 사항
    설명
    Listing mode (for example STANDARD, RANK_BOOST, SESSION).
  • At least one allowed field is required.
  • Emits offer.updated webhook when configured.
  • Stock is managed via GET/POST /api/v1/offers/:urlOrId/stock. Option prices, media, and attributes are not editable via this endpoint yet.
DELETE/api/v1/offers/:urlOrId
offers:write

삭제 또는 보관

판매자 UI와 동일한 삭제/보관 규칙입니다.

요청

  • urlOrId
    path
    유형
    string
    필수
    필수
    설명
    Offer.url slug or Offer.id.
  • Response: { ok: true }.
POST/api/v1/offers/:urlOrId/publish
offers:write

제안 게시

초안을 게시(또는 가시성을 변경)합니다. 필수 목록 필드가 불완전하면 400 오류가 발생합니다.

요청

  • urlOrId
    path
    유형
    string
    필수
    필수
    설명
    Offer.url slug or Offer.id.
  • visibility
    body
    유형
    string
    필수
    선택 사항
    설명
    Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED.
  • Response: { offer: Offer }.
  • Fails if the listing is incomplete for publish.

재고 API

티어별 구매 가능한 수량을 확인하고, 오른쪽 목록에 맞는 배달 필드 이름을 매칭한 후, 수량 또는 저장된 키와 계정을 보충하세요.

매칭 작동 방식

GET /api/v1/stock?fields=username,password는 해당 필드를 가진 목록을 찾습니다. 옵션 이름(또는 optionId)과 필드 이름으로 재고를 보충하세요. 내부 필드 ID는 필요하지 않습니다. 응답에는 절대 자격 증명 값이 포함되지 않습니다.

GET/api/v1/stock
offers:read

목록의 재고 나열

티어별 수량과 배달 필드 이름을 반환하여 키와 계정을 올바른 오퍼에 매칭할 수 있습니다. q, fields, stockMode 및 lowStock으로 필터링하세요. 절대 자격 증명 값을 반환하지 않습니다.

요청

  • q
    query
    유형
    string
    필수
    선택 사항
    제한
    Max 80
    설명
    Filter by listing title or url slug.
  • fields
    query
    유형
    string
    필수
    선택 사항
    설명
    Comma-separated delivery field names. The listing must have all of them (Username,Password). Names match case-insensitively.
  • stockMode
    query
    유형
    string
    필수
    선택 사항
    설명
    QUANTITY or COMPLEX. Listing must have at least one option in that mode.
  • lowStock
    query
    유형
    number
    필수
    선택 사항
    설명
    Keep listings that have a finite tier with available less than or equal to this number.
  • archive
    query
    유형
    string
    필수
    선택 사항
    설명
    One of "active" (default), "archived", or "all".
  • Response: { offers: StockOffer[], total: number }. Numeric offer id is omitted. Option id is included so you can restock a specific tier.
  • available is the buyable count. null with unlimited true means unlimited quantity or on-demand COMPLEX inventory.
  • fields[] is the listing delivery schema (empty for quantity-only listings). Use it to map keys and accounts without field ids.
  • This endpoint never returns credential values.
GET/api/v1/offers/:urlOrId/stock
offers:read

하나의 목록에 대한 재고 가져오기

하나의 URL 또는 숫자 ID에 대한 StockOffer 형태와 동일합니다. 수량만 포함됩니다.

요청

  • urlOrId
    path
    유형
    string
    필수
    필수
    설명
    Offer.url slug or Offer.id.
  • Response: { offer } with the same StockOffer shape as GET /api/v1/stock.
  • Counts only. Use the seller editor to inspect saved key values.
POST/api/v1/offers/:urlOrId/stock
offers:write

목록 재고 보충

수량 티어: 추가, 제거 또는 설정. 저장된 항목 티어: 필드 이름별 항목 객체, 하나의 필드가 있을 때 keys[], 또는 구분된 텍스트. 여러 티어를 하나의 호출로 options[]를 통해 전달합니다. dryRun은 매칭 미리보기를 제공합니다. onDuplicate는 기본적으로 건너뛰기로 설정됩니다.

json
{
  "option": "1 Month",
  "add": 50
}

요청

  • urlOrId
    path
    유형
    string
    필수
    필수
    설명
    Offer.url slug or Offer.id.
  • option
    body
    유형
    string
    필수
    조건부
    설명
    Pricing option name (case-insensitive). Omit when the listing has a single tier.
  • optionId
    body
    유형
    number
    필수
    조건부
    설명
    Pricing option id from GET stock. Wins over option when both are sent. Ambiguous names return 409 OPTION_AMBIGUOUS.
  • add
    body
    유형
    number
    필수
    조건부
    제한
    1-1,000,000
    설명
    QUANTITY: add this many units. Fails with 400 UNLIMITED_STOCK if the tier is unlimited.
  • remove
    body
    유형
    number
    필수
    조건부
    제한
    1-1,000,000
    설명
    QUANTITY: withdraw this many units. Fails with 400 INSUFFICIENT_STOCK when there is not enough.
  • set
    body
    유형
    number | null
    필수
    조건부
    설명
    QUANTITY: set an absolute count. null means unlimited. Cannot go below units held in checkout.
  • items
    body
    유형
    object[]
    필수
    조건부
    제한
    Max 1,000
    설명
    COMPLEX: objects keyed by delivery field name, for example { "Username": "a", "Password": "b" }. Names match case-insensitively.
  • keys
    body
    유형
    string[]
    필수
    조건부
    제한
    Max 1,000
    설명
    COMPLEX: license keys when the listing has exactly one delivery field. Otherwise 400 FIELD_MAPPING_AMBIGUOUS.
  • text
    body
    유형
    string
    필수
    조건부
    제한
    Max 1,000 rows
    설명
    COMPLEX: delimited paste. A header row that matches field names is detected automatically. Otherwise columns map in field sort order when the column count matches.
  • delimiter
    body
    유형
    string
    필수
    선택 사항
    제한
    Default :
    설명
    Delimiter for text. Ignored unless text is sent.
  • headers
    body
    유형
    string[]
    필수
    선택 사항
    설명
    Optional column headers for text when the first line is data, not names.
  • options
    body
    유형
    object[]
    필수
    조건부
    설명
    Restock several tiers in one call. Each element is the same shape as a single-option body (option, add, items, …).
  • dryRun
    body
    유형
    boolean
    필수
    선택 사항
    설명
    Preview matching and counts without writing. Default false.
  • onDuplicate
    body
    유형
    string
    필수
    선택 사항
    제한
    skip (default) or error
    설명
    COMPLEX: skip existing unsold fingerprints, or fail the request with 409 DUPLICATE_ITEMS.
  • Send exactly one action per option: add, remove, set, items, keys, or text.
  • Sending items to a QUANTITY tier (or add to a COMPLEX tier) returns 400 STOCK_MODE_MISMATCH.
  • Responses never echo credential values. COMPLEX results include imported, skippedDuplicates, errors, and matchedFields.
  • Saved items are capped at 5,000 unsold rows per option. A single request may import at most 1,000 rows.

여러 계정 또는 키 가져오기

가이드

계정에는 items[], 단일 필드 라이센스 코드에는 keys[]를 사용하여 POST /api/v1/offers/:url/stock를 사용하세요. 요청당 1,000행으로 나누어 보내세요.

  1. 1리스트를 가져옵니다. fields[]와 stockMode를 사용하여 아이템, 키 또는 추가를 선택하세요.
  2. 2계정을 JSON 또는 CSV 형식으로 필드 이름으로 키를 지정하여 저장하세요. 라이센스 키는 한 줄에 하나씩 저장하세요.
  3. 3먼저 드라이런을 실행하세요. wouldImport, skippedDuplicates, matchedFields를 확인하세요.
  4. 4재고를 작성하려면 dryRun 없이 동일한 본문을 다시 POST하세요.

리스트와 일치하는 페이로드 선택

먼저 GET stock을 호출하세요. fields[]에 이름이 여러 개 있으면 해당 이름으로 키가 지정된 items 객체를 보내세요 (사용자 이름, 비밀번호, 이메일). 필드가 정확히 하나인 경우 keys[]만으로 충분합니다. 수량 목록은 items가 아닌 add를 사용합니다.

요청당 1,000행. 티어당 5,000개의 미판매 아이템. 분당 300개의 요청. 기본적으로 중복은 건너뜁니다.

accounts.json (계정당 하나의 객체)

json
[
  { "Username": "player1", "Password": "secret1", "E-Mail": "[email protected]" },
  { "Username": "player2", "Password": "secret2", "E-Mail": "[email protected]" }
]

accounts.csv

csv
Username,Password,E-Mail
player1,secret1,p1@example.com
player2,secret2,p2@example.com

keys.txt (한 줄에 하나의 라이센스 키)

text
AAAA-BBBB-CCCC
DDDD-EEEE-FFFF
GGGG-HHHH-IIII

cURL

bash
# Inspect field names and stockMode
curl -s -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock

# Preview (no write)
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"dryRun":true,"onDuplicate":"skip","option":"Premium","items":[{"Username":"player1","Password":"secret1","E-Mail":"[email protected]"}]}'

# Apply accounts
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"onDuplicate":"skip","option":"Premium","items":[{"Username":"player1","Password":"secret1","E-Mail":"[email protected]"}]}'

# Apply license keys (listing must have exactly one delivery field)
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"option":"Steam","keys":["AAAA-BBBB-CCCC","DDDD-EEEE-FFFF"]}'

# Or paste CSV / colon-separated rows in text
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"option":"Premium","delimiter":",","text":"Username,Password,E-Mail\nplayer1,secret1,[email protected]"}'

TypeScript 배치 가져오기 (요청당 1,000행)

typescript
// Paste rmtFetch and withRetry from the Auth section first.
const CHUNK = 1000;

async function importRows(offerUrl: string, option: string, rows: Array<Record<string, string>>) {
  for (let i = 0; i < rows.length; i += CHUNK) {
    const items = rows.slice(i, i + CHUNK);
    const preview = await rmtFetch<{
      results: Array<{ wouldImport: number; skippedDuplicates: number; errors: string[] }>;
    }>(`/offers/${offerUrl}/stock`, {
      method: "POST",
      body: JSON.stringify({ dryRun: true, onDuplicate: "skip", option, items }),
    });
    const row = preview.results[0];
    if ((row?.errors?.length ?? 0) > 0) {
      throw new Error(row.errors.join("; "));
    }
    await withRetry(() =>
      rmtFetch(`/offers/${offerUrl}/stock`, {
        method: "POST",
        body: JSON.stringify({ onDuplicate: "skip", option, items }),
      }),
    );
  }
}

// License keys: only when GET stock.fields has exactly one name
async function importKeys(offerUrl: string, option: string, keys: string[]) {
  for (let i = 0; i < keys.length; i += CHUNK) {
    await withRetry(() =>
      rmtFetch(`/offers/${offerUrl}/stock`, {
        method: "POST",
        body: JSON.stringify({ option, keys: keys.slice(i, i + CHUNK) }),
      }),
    );
  }
}

이 API가 보호하는 것

키는 offers:write가 필요하며, 속도 제한이 있으며, 자신의 목록만 재고할 수 있습니다. GET은 저장된 자격 증명을 반환하지 않습니다. POST 응답은 사용자 이름, 비밀번호 또는 키 값을 에코하지 않습니다. 프로덕션에서는 HTTPS를 통해 본문을 전송하고 API 키를 환경 변수에 보관하세요.

Windows에서는 curl.exe를 사용하세요 (curl 별칭이 아님). PowerShell이 JSON을 분할하지 않도록 -d JSON을 따옴표로 묶으세요.

주문 API

주문은 판매자 계정에 한정됩니다. 구매자 청구 세부정보는 기록된 마켓플레이스 개인 정보 보호 규칙에 따라 수정될 수 있습니다.

GET/api/v1/orders
orders:read

판매자 주문 목록

limit, offset, status, q 및 sort(최신, 오래된, total_high, total_low)를 지원합니다.

요청

  • limit
    query
    유형
    number
    필수
    선택 사항
    제한
    1-100, default 20
    설명
    Page size.
  • offset
    query
    유형
    number
    필수
    선택 사항
    제한
    >= 0, default 0
    설명
    Skip this many rows.
  • status
    query
    유형
    string
    필수
    선택 사항
    제한
    Max 32
    설명
    Filter by order status (for example PAID, DELIVERED, COMPLETED).
  • q
    query
    유형
    string
    필수
    선택 사항
    제한
    Max 80
    설명
    Search reference or related text.
  • sort
    query
    유형
    string
    필수
    선택 사항
    제한
    newest (default)
    설명
    newest | oldest | total_high | total_low.
  • Response: { orders: Order[], total: number }.
GET/api/v1/orders/:uid
orders:read

하나의 주문 가져오기

라인 항목이 포함된 주문을 반환합니다. 공개 주문 uid를 사용하십시오.

요청

  • uid
    path
    유형
    string
    필수
    필수
    설명
    Order.uid.
  • Response: { order } with line items.
  • Buyer billing fields may be redacted under marketplace-of-record privacy rules.
POST/api/v1/orders/:uid/deliver
orders:write

배송 완료로 표시

수동 이행. COMPLEX 라인은 필수일 때 완전히 첨부되어야 합니다. order.delivered를 방출합니다.

요청

  • uid
    path
    유형
    string
    필수
    필수
    설명
    Order.uid.
  • evidence
    body
    유형
    string[]
    필수
    선택 사항
    제한
    HTTPS, max 10
    설명
    Optional screenshot or transfer-proof URLs.
  • Response: { success: true, order }.
  • COMPLEX inventory lines must be fully attached before deliver when the product requires it.
  • Emits order.delivered webhook when configured.

호스팅 결제

승인된 파트너 상점이나 백엔드는 구매자를 RMT.GG 결제 페이지로 보낼 수 있습니다. 우리는 기록된 상인이며 잠금 금액의 4%를 차지합니다.

허용 목록 및 이행

설정에서 호스팅된 체크아웃을 적용한 후, API 키와 JSON 웹훅을 생성하세요. 결제 후 checkout.completed 이벤트가 발생합니다. 배송 값은 RMT.GG 확인서에만 있으며, 판매자 GET 또는 웹훅에는 포함되지 않습니다.

POST/api/v1/checkout/sessions
checkout:write

호스팅 결제 세션 생성

구매자를 잠긴 RMT.GG 결제 페이지로 보냅니다. 하나의 아이템: 금액과 itemName. 장바구니: 각 줄에 이름과 금액이 있는 items[]. 통화는 기본적으로 USD입니다. 결제 후, 배송 정보가 복사될 때까지 구매자는 RMT.GG에 남아 있습니다. returnUrl은 상점으로 계속 이동합니다; 배송이 없으면 짧은 카운트다운 후에 다시 보내드립니다. 금액, 길이 및 기타 제한은 Limits 열에 있습니다.

javascript
{
  amount:   10,           // what the buyer pays
  itemName: "Gold pack",  // pay page heading
}

요청

  • amount
    body
    유형
    number
    필수
    조건부
    제한
    > 0, max 1,000,000
    설명
    What the buyer pays. Required for a single item. With items[], omit it or send the line sum. Mismatch: 400 AMOUNT_MISMATCH.
  • currency
    body
    유형
    string
    필수
    선택 사항
    제한
    Default USD
    설명
    ISO 4217 code such as USD or EUR.
  • itemName
    body
    유형
    string
    필수
    조건부
    제한
    Max 120
    설명
    Pay page heading. Required for a single item. Alias: title. With items[], defaults to the first line name. Missing: 400 ITEM_NAME_REQUIRED.
  • title
    body
    유형
    string
    필수
    선택 사항
    설명
    Alias of itemName. If both are sent, itemName wins.
  • description
    body
    유형
    string
    필수
    선택 사항
    제한
    Max 200
    설명
    Copy under the heading. If omitted, the heading is reused.
  • imageUrl
    body
    유형
    string
    필수
    선택 사항
    제한
    HTTPS, max 2048
    설명
    Product image, or fallback for lines without imageUrl. Invalid: 400 INVALID_IMAGE_URL.
  • items
    body
    유형
    object[]
    필수
    조건부
    제한
    1-20 lines, JSON max 48,000
    설명
    Locked cart. Required when amount is omitted. Buyers cannot change lines. Empty: 400 INVALID_ITEMS.
  • items[].name
    body
    유형
    string
    필수
    필수
    제한
    Max 120
    설명
    Line title. Alias: title.
  • items[].title
    body
    유형
    string
    필수
    선택 사항
    설명
    Alias of items[].name. If both are sent, name wins.
  • items[].description
    body
    유형
    string
    필수
    선택 사항
    제한
    Max 200
    설명
    Line copy under the name.
  • items[].amount
    body
    유형
    number
    필수
    필수
    제한
    > 0, max 1,000,000
    설명
    Unit price. Session total is sum(amount * quantity).
  • items[].quantity
    body
    유형
    number
    필수
    선택 사항
    제한
    1-99, default 1
    설명
    Locked on the pay page.
  • items[].imageUrl
    body
    유형
    string
    필수
    선택 사항
    제한
    HTTPS, max 2048
    설명
    Line image. Falls back to top-level imageUrl.
  • items[].delivery
    body
    유형
    object[]
    필수
    선택 사항
    제한
    Max 16 fields
    설명
    Shown after payment on RMT.GG. Seller GET and webhooks omit values.
  • items[].delivery[].name
    body
    유형
    string
    필수
    필수
    제한
    Max 80
    설명
    Field label, for example Code or Password.
  • items[].delivery[].type
    body
    유형
    string
    필수
    선택 사항
    제한
    text, password, textarea
    설명
    password is blurred until the buyer reveals it. Default text.
  • items[].delivery[].value
    body
    유형
    string
    필수
    필수
    제한
    Max 2048
    설명
    Field value. Numbers are stored as strings. Empty: 400 INVALID_DELIVERY.
  • email
    body
    유형
    string
    필수
    선택 사항
    제한
    Invalid values ignored
    설명
    Prefills the pay page. The buyer still confirms email before paying.
  • returnUrl
    body
    유형
    string
    필수
    선택 사항
    제한
    HTTPS, max 2048
    설명
    Continue-to-shop after payment. Delivery fields keep the buyer on RMT.GG with a button. No delivery: we send them back after a short countdown. If omitted, there is no shop button. http://localhost is allowed for local shops.
  • cancelUrl
    body
    유형
    string
    필수
    선택 사항
    제한
    HTTPS, max 2048
    설명
    Redirect if the buyer cancels or the session expires. If omitted, they stay on the pay page.
  • invoiceId
    body
    유형
    string
    필수
    선택 사항
    제한
    Max 128
    설명
    Your shop id. Same payload returns the existing session. A different payload: 409 INVOICE_CONFLICT.
  • categorySlug
    body
    유형
    string
    필수
    조건부
    제한
    With offering, or omit both
    설명
    Public root slug such as games. Used for card, PayPal, and crypto labels, not the pay page title. Wrong pair: 400 INVALID_PSP_CATEGORY.
  • offering
    body
    유형
    string
    필수
    조건부
    제한
    With categorySlug, or omit both
    설명
    Catalog offering such as Mods. Mapped to labels like Games · Add-ons.
  • metadata
    body
    유형
    object
    필수
    선택 사항
    제한
    Object, max 4096 chars
    설명
    Stored on the session. Not returned on seller GET.
  • Idempotency-Key
    header
    유형
    string
    필수
    선택 사항
    제한
    Max 128
    설명
    Replay header. Same key and payload returns the existing session. A different payload: 409 IDEMPOTENCY_CONFLICT.

응답

  • uid
    response
    유형
    string
    설명
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    response
    유형
    string
    설명
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    response
    유형
    boolean
    설명
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    response
    유형
    number
    설명
    Locked buyer total in major units.
  • currency
    response
    유형
    string
    설명
    ISO currency code stored on the session (for example USD).
  • itemName
    response
    유형
    string | null
    설명
    Pay page heading.
  • description
    response
    유형
    string
    설명
    Longer copy under the heading.
  • email
    response
    유형
    string | null
    설명
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    response
    유형
    string | null
    설명
    Buyer locale when known. Not a create-session field.
  • returnUrl
    response
    유형
    string | null
    설명
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    response
    유형
    string | null
    설명
    Cancel/expiry redirect, or null.
  • invoiceId
    response
    유형
    string | null
    설명
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    response
    유형
    string | null
    설명
    Same as invoiceId (legacy alias).
  • source
    response
    유형
    string
    설명
    How the session was created. API sessions are "api".
  • expiresAt
    response
    유형
    string
    설명
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    response
    유형
    string
    설명
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    response
    유형
    string | null
    설명
    Marketplace order uid after payment. null until the session is paid.
  • items
    response
    유형
    object[]
    설명
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • hosted_url
    response
    유형
    string
    설명
    Pay page URL. Send the buyer here. Same target as hostedUrl.
  • expires_at
    response
    유형
    string
    설명
    ISO timestamp. Same value as expiresAt.
  • Approved partners only. Platform fee is 4% of the locked amount. Payment-method costs are absorbed by the platform.
  • After payment the buyer stays on RMT.GG so they can copy delivery fields. returnUrl is a continue button when delivery is present. With no delivery fields we send them back after a short countdown. If you omit them, they stay on the pay page after payment, cancel, or expiry.
  • Fulfill on checkout.completed. Sessions expire after 24 hours. Buyers cannot change line items. imageUrl must be HTTPS. categorySlug and offering must be sent together (or omit both); a wrong pair returns 400 INVALID_PSP_CATEGORY.
GET/api/v1/checkout/sessions/:uid
checkout:write

호스팅 결제 세션 가져오기

생성한 세션을 반환합니다. checkout.completed가 지연될 경우 이 값을 사용하세요. paid는 상태가 paid일 때만 true입니다. items에는 배송 값이 포함되지 않습니다.

요청

  • uid
    path
    유형
    string
    필수
    필수
    설명
    Session uid returned at create time.

응답

  • uid
    response
    유형
    string
    설명
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    response
    유형
    string
    설명
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    response
    유형
    boolean
    설명
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    response
    유형
    number
    설명
    Locked buyer total in major units.
  • currency
    response
    유형
    string
    설명
    ISO currency code stored on the session (for example USD).
  • itemName
    response
    유형
    string | null
    설명
    Pay page heading.
  • description
    response
    유형
    string
    설명
    Longer copy under the heading.
  • email
    response
    유형
    string | null
    설명
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    response
    유형
    string | null
    설명
    Buyer locale when known. Not a create-session field.
  • returnUrl
    response
    유형
    string | null
    설명
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    response
    유형
    string | null
    설명
    Cancel/expiry redirect, or null.
  • invoiceId
    response
    유형
    string | null
    설명
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    response
    유형
    string | null
    설명
    Same as invoiceId (legacy alias).
  • source
    response
    유형
    string
    설명
    How the session was created. API sessions are "api".
  • expiresAt
    response
    유형
    string
    설명
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    response
    유형
    string
    설명
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    response
    유형
    string | null
    설명
    Marketplace order uid after payment. null until the session is paid.
  • items
    response
    유형
    object[]
    설명
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • Response: { session }. Stale unpaid sessions are marked expired before they are returned.
  • Use this as a backup to checkout.completed. paid is true only when status is paid.
  • 404 NOT_FOUND if the uid is unknown or belongs to another seller.
GET/api/v1/checkout/sessions
checkout:write

송장 ID로 호스팅된 체크아웃 세션 조회하기

uid로 GET한 것과 동일한 세션 객체입니다. 생성 시 보낸 invoiceId를 전달하세요. 누락: 400 INVOICE_ID_REQUIRED. 알 수 없음: 404 NOT_FOUND.

요청

  • invoiceId
    query
    유형
    string
    필수
    필수
    제한
    Max 128
    설명
    invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID.

응답

  • uid
    response
    유형
    string
    설명
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    response
    유형
    string
    설명
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    response
    유형
    boolean
    설명
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    response
    유형
    number
    설명
    Locked buyer total in major units.
  • currency
    response
    유형
    string
    설명
    ISO currency code stored on the session (for example USD).
  • itemName
    response
    유형
    string | null
    설명
    Pay page heading.
  • description
    response
    유형
    string
    설명
    Longer copy under the heading.
  • email
    response
    유형
    string | null
    설명
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    response
    유형
    string | null
    설명
    Buyer locale when known. Not a create-session field.
  • returnUrl
    response
    유형
    string | null
    설명
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    response
    유형
    string | null
    설명
    Cancel/expiry redirect, or null.
  • invoiceId
    response
    유형
    string | null
    설명
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    response
    유형
    string | null
    설명
    Same as invoiceId (legacy alias).
  • source
    response
    유형
    string
    설명
    How the session was created. API sessions are "api".
  • expiresAt
    response
    유형
    string
    설명
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    response
    유형
    string
    설명
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    response
    유형
    string | null
    설명
    Marketplace order uid after payment. null until the session is paid.
  • items
    response
    유형
    object[]
    설명
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • Same { session } body as GET /api/v1/checkout/sessions/:uid, including the fields above.
  • Prefer this when you stored your own invoice id and not the session uid.
  • 404 NOT_FOUND if no session exists for that invoice id.

아웃바운드 웹훅

설정 → 개발자에서 HTTPS 엔드포인트(또는 Discord 웹훅)를 구성합니다. 구독된 이벤트가 발생하면 RMT가 POST합니다.

order.paid
order.delivered
order.completed
order.refunded
order.disputed
offer.published
offer.updated
checkout.completed
checkout.canceled
checkout.refunded
  • JSON 형식은 id, type, created 및 data가 포함된 구조화된 봉투를 게시합니다.
  • Discord 형식은 주문 또는 제안 링크가 포함된 풍부한 임베드를 게시합니다.
  • 선택적 서명은 X-RMT-Timestamp 및 X-RMT-Signature를 사용합니다(예약과 동일한 방식).
  • 배송 기록은 각 엔드포인트 아래에 나타나므로 실패를 재시도할 수 있습니다. 엔드포인트는 반복적인 실패 후 자동으로 일시 중지됩니다.
  • 호스팅된 체크아웃은 데이터.checkout와 함께 checkout.completed, checkout.canceled, checkout.refunded를 전송합니다. 배송 값은 생략됩니다. 마켓플레이스 판매는 order.paid 및 기타 order.* 이벤트를 유지합니다.

JSON 배송 봉투

json
{
  "id": "whd_…",
  "type": "order.paid",
  "created": "2026-07-23T12:00:00.000Z",
  "data": {
    "order": {
      "uid": "ord_…",
      "reference": "RMT-…",
      "status": "PAID",
      "url": "https://rmt.gg/orders/ord_…",
      "items": [ /* line items with offer names */ ]
    }
  }
}

서명된 배송 헤더

json
{
  "X-RMT-Event": "order.paid",
  "X-RMT-Delivery": "whd_…",
  "X-RMT-Timestamp": "1710000000",
  "X-RMT-Signature": "v1=abc123…"
}

체크아웃 완료 페이로드

json
{
  "id": "whd_…",
  "type": "checkout.completed",
  "created": "2026-08-14T12:00:00.000Z",
  "data": {
    "checkout": {
      "uid": "pcs_…",
      "status": "paid",
      "amount": 10,
      "currency": "USD",
      "itemName": "Gold pack",
      "description": "1000 gold for account example",
      "invoiceId": "inv-12345",
      "source": "api",
      "orderUid": "ord_…",
      "hostedUrl": "https://rmt.gg/pay/pcs_…",
      "email": "[email protected]",
      "paidAt": "2026-08-14T12:01:00.000Z",
      "expiresAt": "2026-08-15T12:00:00.000Z",
      "createdAt": "2026-08-14T12:00:00.000Z",
      "reason": null,
      "items": [
        {
          "name": "Gold pack",
          "description": "1000 gold for account example",
          "amount": 10,
          "quantity": 1,
          "imageUrl": "https://cdn.shop.example/gold.png"
        }
      ]
    }
  }
}

checkout.canceled 페이로드

json
{
  "id": "whd_…",
  "type": "checkout.canceled",
  "created": "2026-08-14T12:20:00.000Z",
  "data": {
    "checkout": {
      "uid": "pcs_…",
      "status": "canceled",
      "amount": 10,
      "currency": "USD",
      "itemName": "Gold pack",
      "description": "1000 gold for account example",
      "invoiceId": "inv-12345",
      "source": "api",
      "orderUid": null,
      "hostedUrl": "https://rmt.gg/pay/pcs_…",
      "email": "[email protected]",
      "paidAt": null,
      "expiresAt": "2026-08-15T12:00:00.000Z",
      "createdAt": "2026-08-14T12:00:00.000Z",
      "reason": "buyer_canceled",
      "items": [
        {
          "name": "Gold pack",
          "description": "1000 gold for account example",
          "amount": 10,
          "quantity": 1,
          "imageUrl": "https://cdn.shop.example/gold.png"
        }
      ]
    }
  }
}

웹훅 서명 확인

서명 비밀이 설정되면 timestamp + '.' + rawBody에 대해 HMAC-SHA256을 계산하고 v1= 이후의 16진수와 비교합니다.

비밀 키는 RMT에 유지됩니다. 각 서명된 POST에는 X-RMT-Timestamp (유닉스 초)와 X-RMT-Signature (v1= 및 헥스)가 포함됩니다. 비밀 키를 사용하여 문자열 timestamp + '.' + rawBody에 대해 HMAC-SHA256을 계산한 후 v1= 뒤의 헥스와 비교합니다. 5분 이상 된 타임스탬프는 거부합니다.

  • 수신된 원시 바이트를 그대로 읽습니다. JSON을 파싱하거나 해싱 전에 재직렬화하지 마세요.
  • X-RMT-Timestamp 헤더 값을 타임스탬프 접두사로 사용하세요 (같은 문자열, 재형식화하지 않음).
  • 타이밍 안전 비교를 사용하여 비교하세요. 비밀이 설정된 경우 서명이 누락되거나 불일치하는 요청은 거부합니다.
  • 재생을 제한하기 위해 5분 이상 된 타임스탬프는 거부합니다. 동일한 방식이 reserve.item 및 아웃바운드 주문 또는 체크아웃 이벤트에 적용됩니다.

타입스크립트 검증 (타이밍 안전 비교 및 5분 재생 창)

typescript
import { createHmac, timingSafeEqual } from "node:crypto";

const MAX_AGE_SEC = 5 * 60; // reject replays older than 5 minutes

export function verifyRmtSignature(opts: {
  secret: string;
  timestamp: string | null | undefined;
  signatureHeader: string | null | undefined;
  rawBody: string; // exact POST bytes. Do not JSON.parse then re-stringify.
  nowSec?: number;
}): boolean {
  const secret = opts.secret.trim();
  const timestamp = String(opts.timestamp ?? "").trim();
  const provided = String(opts.signatureHeader ?? "").trim().replace(/^v1=/i, "");
  if (!secret || !timestamp || !provided) return false;

  const ts = Number(timestamp);
  if (!Number.isInteger(ts) || ts <= 0) return false;
  const nowSec = opts.nowSec ?? Math.floor(Date.now() / 1000);
  if (Math.abs(nowSec - ts) > MAX_AGE_SEC) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${opts.rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(provided.toLowerCase(), "utf8");
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}

// Express / Node HTTP example:
// const rawBody = (req as { rawBody?: string }).rawBody
//   ?? JSON.stringify(req.body); // only if you captured the raw string first
// const ok = verifyRmtSignature({
//   secret: process.env.RMT_WEBHOOK_SECRET!,
//   timestamp: req.headers["x-rmt-timestamp"] as string,
//   signatureHeader: req.headers["x-rmt-signature"] as string,
//   rawBody,
// });
// if (!ok) return res.status(401).end();

타입스크립트 웹훅 핸들러

typescript
type CheckoutCompleted = {
  id: string;
  type: "checkout.completed";
  created: string;
  data: {
    checkout: {
      uid: string;
      status: "paid";
      amount: number;
      currency: string;
      itemName: string | null;
      description: string;
      invoiceId: string | null;
      source: string | null;
      orderUid: string | null;
      hostedUrl: string;
      email: string | null;
      paidAt: string | null;
      expiresAt: string | null;
      createdAt: string | null;
      reason: null;
      items: Array<{
        name: string;
        description: string | null;
        amount: number;
        quantity: number;
        imageUrl: string | null;
      }>;
    };
  };
};

type CheckoutCanceled = {
  type: "checkout.canceled";
  data: {
    checkout: {
      uid: string;
      status: "canceled" | "expired";
      invoiceId: string | null;
      reason: "buyer_canceled" | "expired";
    };
  };
};

export async function handleRmtWebhook(rawBody: string, headers: Headers) {
  const ok = verifyRmtSignature({
    secret: process.env.RMT_WEBHOOK_SECRET!,
    timestamp: headers.get("x-rmt-timestamp"),
    signatureHeader: headers.get("x-rmt-signature"),
    rawBody,
  });
  if (!ok) throw new Response("Unauthorized", { status: 401 });

  const event = JSON.parse(rawBody) as { type: string; data: Record<string, unknown> };
  switch (event.type) {
    case "checkout.completed": {
      const checkout = (event as CheckoutCompleted).data.checkout;
      if (!checkout.invoiceId || !checkout.paidAt) break;
      await fulfillShopOrder(checkout.invoiceId, checkout.orderUid);
      break;
    }
    case "checkout.canceled": {
      const checkout = (event as CheckoutCanceled).data.checkout;
      await markShopOrderCanceled(checkout.invoiceId, checkout.reason);
      break;
    }
    case "checkout.refunded":
    case "order.paid":
    case "order.delivered":
      break;
    default:
      break;
  }
}

웹훅 예약(재고 보충)

COMPLEX(고유 단위) 목록의 경우, RMT는 결제 후 로컬 재고가 부족할 때 다음 라이센스, 계정 또는 키를 발행하기 위해 HTTPS 엔드포인트에 POST할 수 있습니다.

결제 안전 실패

엔드포인트가 시간 초과되거나 잘못된 데이터를 반환하면 주문은 PAID 상태로 유지됩니다. 구매자는 요금이 청구되며, 주문에서 오류를 보고 수동으로 예약하거나 키를 첨부할 수 있습니다.

설정하는 방법

  1. 아이템 필드가 포함된 복잡한 오퍼를 생성하세요 (예: 라이센스).
  2. 아이템 단계에서 주문형 인벤토리 엔드포인트를 활성화하고 공개 HTTPS URL을 붙여넣습니다.
  3. RMT가 모든 호출에 X-RMT-Timestamp 및 X-RMT-Signature를 전송하도록 서명 비밀을 선택적으로 설정하세요.
  4. 테스트를 실행하거나 샘플 JSON을 붙여넣고, 응답 경로를 아이템 필드에 매핑한 후 저장하세요.
  5. 리스트를 게시하세요. 구매자는 빈 로컬 재고로 구매할 수 있으며, 결제 후 키가 생성됩니다.
  • 로컬 재고가 항상 우선시되며, 웹훅은 부족한 부분만 채웁니다.
  • 제안 편집기의 항목 단계에서 기본값을 제안 수준으로 구성하거나 가격 옵션별로 재정의합니다.
  • HTTPS만 사용합니다. 선택적 HMAC 서명은 아웃바운드 웹훅과 일치합니다(X-RMT-Event: reserve.item).
  • 편집기에서 테스트 시 dryRun: true를 전송합니다. 주문 페이지에서 엔드포인트를 수정한 후 '예약 재시도'를 사용합니다.

정규 POST 본문(잘림)

json
{
  "id": "rsv_…",
  "type": "reserve.item",
  "order": { "uid": "ord_…", "reference": "RMT-…", "url": "https://rmt.gg/orders/ord_…" },
  "offer": { "url": "my-offer", "title": "Game key", "pageUrl": "https://rmt.gg/offers/my-offer" },
  "option": { "id": 1, "name": "Standard" },
  "fields": [{ "id": 10, "name": "License", "type": "text", "required": true }],
  "quantity": 1
}

요청 헤더 (서명 비밀이 설정된 경우)

json
{
  "Content-Type": "application/json",
  "X-RMT-Event": "reserve.item",
  "X-RMT-Delivery": "rsv_…",
  "X-RMT-Timestamp": "1710000000",
  "X-RMT-Signature": "v1=abc123…"
}

편의 응답

json
{
  "entries": [
    { "name": "License", "value": "AAAA-BBBB-CCCC" }
  ]
}

매핑된 JSON 필드(응답 맵 경로는 $.license와 같은 형식)

json
{
  "license": "AAAA-BBBB-CCCC",
  "email": "[email protected]",
  "password": "temporary-pass"
}

서명 비밀을 확인하는 방법

오퍼에 비밀을 설정하면 모든 reserve POST가 서명됩니다. HMAC-SHA256(secret, timestamp + '.' + rawBody)를 재계산하고 v1= 접두사를 제거한 후 X-RMT-Signature와 비교하세요. 비밀 자체는 요청에 포함되지 않습니다.

전체 검증 예제를 확인하세요

타입스크립트 예약 핸들러 (검증 후 항목 반환)

typescript
type ReserveRequest = {
  id: string;
  type: "reserve.item";
  dryRun?: boolean;
  quantity: number;
  fields: Array<{ name: string; required?: boolean }>;
};

export async function handleReserve(rawBody: string, headers: Headers) {
  const ok = verifyRmtSignature({
    secret: process.env.RMT_RESERVE_SECRET!,
    timestamp: headers.get("x-rmt-timestamp"),
    signatureHeader: headers.get("x-rmt-signature"),
    rawBody,
  });
  if (!ok) return new Response("Unauthorized", { status: 401 });

  const body = JSON.parse(rawBody) as ReserveRequest;
  if (body.type !== "reserve.item") {
    return Response.json({ error: "Unexpected event" }, { status: 400 });
  }

  const qty = Number(body.quantity);
  if (!Number.isInteger(qty) || qty < 1) {
    return Response.json({ error: "Invalid quantity" }, { status: 400 });
  }

  if (body.dryRun) {
    return Response.json({
      entries: [{ name: "License", value: "TEST-AAAA-BBBB" }],
    });
  }

  const license = await mintLicense(); // your inventory
  return Response.json({
    entries: [{ name: "License", value: license }],
  });
}

결제 전에 예약 호출 금지

RMT는 결제가 성공한 후에만 엔드포인트를 호출하므로, 포기된 체크아웃이 라이센스를 소모하지 않습니다.

오류 및 속도 제한

오류는 JSON { error, code? }를 반환합니다. 오픈 API 트래픽은 API 키당 분당 300 요청으로 제한됩니다.

  • API_KEY_REQUIRED
    401

    Authorization 또는 X-Api-Key 헤더가 누락되었습니다.

  • API_KEY_INVALID
    401

    키가 알 수 없거나, 취소되었거나, 만료되었거나, 개발자 접근이 중단되었습니다.

  • SCOPE_MISSING
    403

    키에 엔드포인트에서 요구하는 범위가 없습니다.

  • RATE_LIMITED
    429

    요청이 너무 많습니다. Retry-After 및 X-RateLimit-Reset을 준수하십시오.

  • CHECKOUT_PARTNER_NOT_APPROVED
    403

    이 판매자는 호스팅 체크아웃에 승인되지 않았습니다.

  • INVALID_JSON
    400

    요청 본문은 JSON이어야 합니다.

  • INVALID_AMOUNT
    400

    금액은 0보다 커야 하며 최대 1,000,000이어야 합니다.

  • UNSUPPORTED_CURRENCY
    400

    통화는 지원되는 ISO 코드가 아닙니다.

  • INVALID_RETURN_URL
    400

    returnUrl 및 cancelUrl은 https여야 합니다 (http://localhost는 로컬 상점에 허용됨).

  • INVALID_PSP_CATEGORY
    400

    categorySlug와 offering은 함께 전송되어야 하며 카탈로그 쌍과 일치해야 합니다.

  • INVALID_INVOICE_ID
    400

    invoiceId는 128자보다 깁니다.

  • INVOICE_ID_REQUIRED
    400

    GET /checkout/sessions는 쿼리 매개변수로 invoiceId가 필요합니다.

  • INVALID_IDEMPOTENCY_KEY
    400

    Idempotency-Key는 128자보다 깁니다.

  • INVALID_METADATA
    400

    metadata는 배열이나 원시 값이 아닌 JSON 객체여야 합니다.

  • METADATA_TOO_LARGE
    400

    직렬화된 메타데이터는 4096자보다 큽니다.

  • IDEMPOTENCY_CONFLICT
    409

    Idempotency-Key가 다른 금액, 통화 또는 항목과 함께 재사용되었습니다.

  • INVOICE_CONFLICT
    409

    invoiceId가 다른 금액, 통화 또는 항목과 함께 재사용되었습니다.

  • INVALID_IMAGE_URL
    400

    imageUrl은 https URL이어야 합니다.

  • ITEM_NAME_REQUIRED
    400

    아이템 이름(itemName) 또는 제목(title)은 아이템이 생략될 경우 필수입니다.

  • INVALID_ITEMS
    400

    items는 비어 있지 않은 잠긴 항목 배열이어야 합니다(최대 20개). 각 항목은 이름과 금액이 필요합니다.

  • TOO_MANY_ITEMS
    400

    items는 20개 이상의 항목을 포함할 수 없습니다.

  • AMOUNT_MISMATCH
    400

    amount는 각 항목의 금액과 수량의 곱의 합과 같아야 합니다.

  • INVALID_DELIVERY
    400

    배송 필드가 유효하지 않습니다. 각 필드는 이름(최대 80)과 값(최대 2048)이 필요합니다. type은 text, password 또는 textarea여야 합니다(기본값은 text). 각 줄당 최대 16개 필드.

  • ITEMS_TOO_LARGE
    400

    직렬화된 아이템 JSON이 48,000자를 초과합니다.

  • NOT_FOUND
    404

    해당 uid 또는 invoiceId에 맞는 호스팅된 체크아웃 세션이 없습니다.

  • RESERVE_FAILED
    400

    예약 웹훅이 시간 초과되었거나 잘못된 데이터를 반환했거나 필수 필드를 놓쳤습니다.

  • OPTION_AMBIGUOUS
    409

    해당 이름과 일치하는 가격 옵션이 둘 이상입니다. GET stock에서 optionId를 전달하세요.

  • OPTION_NOT_FOUND
    404

    이 목록에서 해당 ID 또는 이름과 일치하는 가격 옵션이 없습니다.

  • OPTION_REQUIRED
    400

    이 목록에는 여러 가격 옵션이 있습니다. option 또는 optionId를 전달하세요.

  • UNKNOWN_FIELD
    400

    필드 이름이 이 목록의 배달 스키마와 일치하지 않습니다.

  • FIELD_MAPPING_AMBIGUOUS
    400

    열 또는 키를 배달 필드에 매핑할 수 없습니다. 헤더를 보내거나 필드 이름으로 키가 지정된 항목 객체를 사용하세요.

  • STOCK_MODE_MISMATCH
    400

    해당 페이로드가 옵션의 재고 모드(수량 vs 저장된 항목)와 일치하지 않습니다.

  • IMPORT_TOO_LARGE
    400

    재고 보충 요청은 옵션당 최대 1,000개의 저장된 항목을 가져올 수 있습니다.

  • OPTION_ITEM_CAPACITY
    400

    이 가격 옵션은 이미 최대 5,000개의 판매되지 않은 저장된 항목을 보유하고 있습니다.

  • DUPLICATE_ITEMS
    409

    onDuplicate=error이며 이 옵션에 이미 하나 이상의 항목이 존재합니다.

  • UNLIMITED_STOCK
    400

    이 옵션은 무제한 수량입니다. 먼저 유한 수로 전환하려면 set을 사용하세요.

  • INSUFFICIENT_STOCK
    400

    제거할 수량 재고가 충분하지 않습니다.

  • STOCK_HELD_IN_CHECKOUT
    400

    체크아웃에서 예약된 수량보다 수량을 낮출 수 없습니다.

  • INVALID_RESTOCK
    400

    재고 보충 본문에 필수 작업이 누락되었거나 하나의 옵션에서 add/items가 결합되어 있습니다.

429 처리

Retry-After 초를 사용하여 대기하십시오. 제한을 우회하기 위해 키를 회전하지 마십시오; 제한은 키당 적용되며 모든 판매자에게 동일합니다.

성공적인 응답에는 X-RateLimit-Limit, X-RateLimit-Remaining 및 X-RateLimit-Reset이 포함됩니다.

자동화할 준비가 되셨나요?

개발자 설정에서 키를 생성하고 알림에서 Discord 또는 Telegram을 연결하세요.