RMT.GG/Satıcı geliştirici belgeleri
v1

Satıcı API

Listelemeleri otomatikleştir, satışları yerine getir ve sipariş olaylarını akışla. Ödeme sonrası talep üzerine envanter yenileme için dışa dönük webhook'lar ve rezerv uç noktaları içerir.

REST Açık API

Teklifler ve siparişler için Bearer kimlik doğrulamalı /api/v1, keşif ve hız sınırlama başlıkları ile.

Dışa dönük webhook'lar

Sipariş ve teklif yaşam döngüsü olayları için imzalı HTTPS (veya Discord) teslimatları.

Rezerve / yenile

Yerel envanter azaldığında, ödeme sonrası sunucunuzdan COMPLEX stok oluştur.

Neler inşa edebilirsiniz

Satıcı Açık API, Discord bildirimleri, stok senkronizasyonu, Zapier tarzı otomasyon veya RMT.GG üzerinde özel bir arka ofis isteyen satıcılar içindir.

  • Teklifleri yönetin
    Taslaklar oluşturun, güvenli alanları güncelleyin, yayınlayın ve /api/v1/offers üzerinden arşivleyin.
  • Satışları yerine getirin
    Satıcı siparişlerini listeleyin ve inceleyin, ardından isteğe bağlı kanıt URL'leri ile teslim edildi olarak işaretleyin.
  • Limitin altında kalın
    Her anahtar dakikada 300 istekle sınırlıdır. Yanıtlar X-RateLimit-* başlıklarını içerir.
  • Gerçek zamanlı tepki verin
    Sipariş ve teklif olaylarına abone olun veya rezerv webhook'ları ile COMPLEX envanteri yenileyin.
  • Dükkanınızdan ödeme alın
    Onaylı ortaklar, alıcıları harici bir dükkan üzerinden barındırılan ödeme sayfasına yönlendirebilir ve ardından siparişi yerine getirebilir.

Hızlı başlangıç

Geliştirici ayarlarında bir API anahtarı oluşturun, ardından canlı kataloğu yazdırmak için keşfi çağırın.

  1. 1Ayarlar → Geliştirici'yi açın (ayrı bir etkinleştirme adımı yok).
  2. 2Bir API anahtarı oluşturun ve gizli anahtarı bir kez kopyalayın (rmt_sk_live_…). Bunu gizli yöneticinizde saklayın.
  3. 3Kapsamları, kotaları ve işlemleri doğrulamak için Authorization: Bearer ile GET /api/v1 çağrısı yapın.
GET/api/v1

Keşif belgesi

Kapsamları, kotaları, webhook olaylarını ve tam işlemler kataloğunu döner. Herhangi bir geçerli API anahtarı çalışır.

Kimlik Doğrulama

Her /api/v1 isteğinde canlı gizli anahtarınızı gönderin. Sadece HTTPS tercih edin. Anahtarları kamuya açık istemcilerde veya tarayıcı paketlerinde asla gömülü olarak kullanmayın.

Tercih edilen başlık

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

Alternatif başlık

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

Yeniden kullanılabilir TypeScript istemcisi (Bearer kimlik doğrulama, tipli hatalar, 429 tekrar deneme)

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));
    }
  }
}

Sızıntı durumunda döndürün

Bir anahtar sızarsa, Geliştirici ayarlarında iptal edin ve yenisini oluşturun. Canlıysanız iptal etmeden önce otomasyonunuzu güncelleyin.

Kapsamlar

Her API anahtarı, uç noktaları kontrol eden kapsamları taşır. Eksik kapsam 403 SCOPE_MISSING döner.

offers:read
offers:write
orders:read
orders:write
webhooks:manage
checkout:write
  • offers:read: Tekliflerinizi listeleyin ve alın.
  • offers:write: Teklifleri oluşturun, güncelleyin, yayınlayın ve silin.
  • orders:read: Satıcı siparişlerini listeleyin ve alın.
  • orders:write: Siparişleri teslim edildi olarak işaretleyin.
  • webhooks:manage: Gelecek Open API webhook yönetimi için ayrılmıştır. Bugün Bildirimler altında Discord/Telegram'ı ve Geliştirici ayarlarında JSON webhook'larını yapılandırın.
  • checkout:write: Barındırılan ödeme oturumlarını oluşturma ve okuma. Yönetici onaylı ortak ödeme gerektirir.

Varsayılan anahtar kapsamları

Yeni anahtarlar offers:read, offers:write, orders:read ve orders:write alır. Dışa dönük webhook CRUD, Ayarlar UI'sında kalır (oturum kimlik doğrulaması).

Teklifler API

Teklif tanımlayıcıları, genel url slug'ını veya sayısal kimliği kabul eder. Yanıtlar içsel id ve sellerId'yi içermez.

PATCH'in henüz değiştiremeyeceği şeyler

Stok satırları, seçenek fiyatları, medya ve özellikler satıcı düzenleyicisinde (veya gelecekteki uç noktalar) yönetilir, bugün PATCH ile değil.

GET/api/v1/offers
offers:read

Tekliflerinizi listeleyin

archive=active (varsayılan), arşivlenmiş veya tüm ile filtreleyin.

İstek

  • archive
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    One of "active" (default), "archived", or "all".
  • Response: { offers: Offer[], total: number }. Numeric id and sellerId are omitted.
POST/api/v1/offers
offers:write

Bir taslak teklif oluştur

Kimlik doğrulaması yapılmış satıcıya ait boş bir taslak oluşturur. Gövde gerekli değildir.

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

Bir teklifi al

Genel url slug'ı veya sayısal kimlik ile yükleyin. İlişkiler (seçenekler) dahil edilebilir; stok öğeleri dahil değildir.

İstek

  • urlOrId
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Offer.url slug or Offer.id.
  • Returns relations (options, etc.) when available; items are not included.
PATCH/api/v1/offers/:urlOrId
offers:write

Teklif alanlarını güncelle

Listeleme alanlarının güvenli bir alt kümesini PATCH yapın. Dışa dönük webhook'lar yapılandırıldığında offer.updated yayar.

İstek

  • urlOrId
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Offer.url slug or Offer.id.
  • title
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Listing title.
  • description
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Listing description.
  • visibility
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    PUBLIC | PRIVATE | UNPUBLISHED.
  • categoryId
    İçinde
    body
    Tür
    number
    Gerekli
    Opsiyonel
    Açıklama
    Catalog category id.
  • offeringId
    İçinde
    body
    Tür
    number
    Gerekli
    Opsiyonel
    Açıklama
    Catalog offering id.
  • thumbnail
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Thumbnail URL or asset reference.
  • offerType
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Offer type string used by the listing.
  • listingMode
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    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

Sil veya arşivle

Satıcı UI'si ile aynı sil/arşiv kuralları.

İstek

  • urlOrId
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Offer.url slug or Offer.id.
  • Response: { ok: true }.
POST/api/v1/offers/:urlOrId/publish
offers:write

Bir teklifi yayınla

Bir taslağı (veya görünürlüğü değiştirir) yayınlar. Gerekli listeleme alanları eksikse 400 ile başarısız olur.

İstek

  • urlOrId
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Offer.url slug or Offer.id.
  • visibility
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED.
  • Response: { offer: Offer }.
  • Fails if the listing is incomplete for publish.

Stok API'si

Her seviye için satın alınabilir miktarları görün, teslimat alan adlarını doğru listeyle eşleştirin, ardından miktarı veya kaydedilmiş anahtarları ve hesapları yeniden stoklayın.

Eşlemenin nasıl çalıştığı

GET /api/v1/stock?fields=username,password, bu alanlara sahip listelemeleri bulur. Yeniden stoklamak için seçenek adlarını (veya optionId) ve alan adlarını kullanın. İçsel alan kimliklerine ihtiyacınız yok. Yanıtlar asla kimlik bilgisi değerlerini içermez.

GET/api/v1/stock
offers:read

Listelemelerinizdeki stokları listele

Anahtarları ve hesapları doğru teklife eşleştirebilmeniz için seviye başına miktarları ve teslimat alan adlarını döndürür. q, fields, stockMode ve lowStock ile filtreleyin. Asla kimlik bilgisi değerlerini döndürmez.

İstek

  • q
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 80
    Açıklama
    Filter by listing title or url slug.
  • fields
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Comma-separated delivery field names. The listing must have all of them (Username,Password). Names match case-insensitively.
  • stockMode
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    QUANTITY or COMPLEX. Listing must have at least one option in that mode.
  • lowStock
    İçinde
    query
    Tür
    number
    Gerekli
    Opsiyonel
    Açıklama
    Keep listings that have a finite tier with available less than or equal to this number.
  • archive
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    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

Bir listeleme için stok al

Bir url veya sayısal id için indeksle aynı StockOffer şekli. Sadece miktarları döndürür.

İstek

  • urlOrId
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    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

Bir listelemeyi yeniden stokla

Miktar seviyeleri: ekle, kaldır veya ayarla. Kaydedilmiş öğe seviyeleri: alan adıyla öğe nesneleri, bir alan olduğunda keys[], veya sınırlı metin. Bir çağrıda birden fazla seviye options[] aracılığıyla. dryRun eşleşmeyi önizler. onDuplicate varsayılan olarak atla.

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

İstek

  • urlOrId
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Offer.url slug or Offer.id.
  • option
    İçinde
    body
    Tür
    string
    Gerekli
    Koşullu
    Açıklama
    Pricing option name (case-insensitive). Omit when the listing has a single tier.
  • optionId
    İçinde
    body
    Tür
    number
    Gerekli
    Koşullu
    Açıklama
    Pricing option id from GET stock. Wins over option when both are sent. Ambiguous names return 409 OPTION_AMBIGUOUS.
  • add
    İçinde
    body
    Tür
    number
    Gerekli
    Koşullu
    Limitler
    1-1,000,000
    Açıklama
    QUANTITY: add this many units. Fails with 400 UNLIMITED_STOCK if the tier is unlimited.
  • remove
    İçinde
    body
    Tür
    number
    Gerekli
    Koşullu
    Limitler
    1-1,000,000
    Açıklama
    QUANTITY: withdraw this many units. Fails with 400 INSUFFICIENT_STOCK when there is not enough.
  • set
    İçinde
    body
    Tür
    number | null
    Gerekli
    Koşullu
    Açıklama
    QUANTITY: set an absolute count. null means unlimited. Cannot go below units held in checkout.
  • items
    İçinde
    body
    Tür
    object[]
    Gerekli
    Koşullu
    Limitler
    Max 1,000
    Açıklama
    COMPLEX: objects keyed by delivery field name, for example { "Username": "a", "Password": "b" }. Names match case-insensitively.
  • keys
    İçinde
    body
    Tür
    string[]
    Gerekli
    Koşullu
    Limitler
    Max 1,000
    Açıklama
    COMPLEX: license keys when the listing has exactly one delivery field. Otherwise 400 FIELD_MAPPING_AMBIGUOUS.
  • text
    İçinde
    body
    Tür
    string
    Gerekli
    Koşullu
    Limitler
    Max 1,000 rows
    Açıklama
    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
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Default :
    Açıklama
    Delimiter for text. Ignored unless text is sent.
  • headers
    İçinde
    body
    Tür
    string[]
    Gerekli
    Opsiyonel
    Açıklama
    Optional column headers for text when the first line is data, not names.
  • options
    İçinde
    body
    Tür
    object[]
    Gerekli
    Koşullu
    Açıklama
    Restock several tiers in one call. Each element is the same shape as a single-option body (option, add, items, …).
  • dryRun
    İçinde
    body
    Tür
    boolean
    Gerekli
    Opsiyonel
    Açıklama
    Preview matching and counts without writing. Default false.
  • onDuplicate
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    skip (default) or error
    Açıklama
    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.

Birden fazla hesap veya anahtar içe aktar

Kılavuz

Hesaplar için items[], tek alan lisans kodları için keys[] ile POST /api/v1/offers/:url/stock kullanın. Her istekte 1,000 satıra kadar bölün.

  1. 1İlanı AL. Öğeleri, anahtarları veya eklemek için fields[] ve stockMode kullanın.
  2. 2Hesapları alan adıyla anahtarlandırılmış JSON veya CSV olarak kaydedin. Lisans anahtarlarını her satıra bir tane olacak şekilde kaydedin.
  3. 3Önce kuru çalıştırma yapın. wouldImport, skippedDuplicates ve matchedFields'i kontrol edin.
  4. 4Stok yazmak için aynı gövdeyi dryRun olmadan tekrar POST edin.

Listeyle eşleşen yükü seçin

Önce GET stock çağrısı yapın. Eğer fields[] birden fazla isim içeriyorsa, bu isimlerle anahtarlanan items nesnelerini gönderin (Kullanıcı Adı, Şifre, E-Posta). Eğer tam olarak bir alan varsa, keys[] yeterlidir. Miktar listeleri için add kullanın, items değil.

Her istekte 1.000 satır. Her seviye için 5.000 satılmamış öğe. Dakikada 300 istek. Varsayılan olarak kopyalar atlanır.

accounts.json (her hesap için bir nesne)

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 (her satırda bir lisans anahtarı)

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 toplu içe aktarma (istek başına 1,000 satır)

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) }),
      }),
    );
  }
}

Bu API'nin koruduğu şeyler

Anahtarlar offers:write gerektirir, oran sınırlıdır ve yalnızca kendi listelemelerinizi yeniden stoklayabilir. GET asla kaydedilmiş kimlik bilgilerini döndürmez. POST yanıtları Kullanıcı Adı, Şifre veya anahtar değerlerini yansıtmaz. Üretimde gövdeyi HTTPS üzerinden gönderin ve API anahtarını bir ortam değişkeninde saklayın.

Windows'ta curl.exe kullanın (curl takma adını değil). PowerShell'in bölmemesi için -d JSON'u tırnak içine alın.

Siparişler API

Siparişler, satıcı hesabınıza özgüdür. Alıcı fatura detayları, kayıtlı pazar gizlilik kuralları altında gizlenebilir.

GET/api/v1/orders
orders:read

Satıcı siparişlerini listele

limit, offset, durum, q ve sıralama (en yeni, en eski, toplam_yüksek, toplam_düşük) destekler.

İstek

  • limit
    İçinde
    query
    Tür
    number
    Gerekli
    Opsiyonel
    Limitler
    1-100, default 20
    Açıklama
    Page size.
  • offset
    İçinde
    query
    Tür
    number
    Gerekli
    Opsiyonel
    Limitler
    >= 0, default 0
    Açıklama
    Skip this many rows.
  • status
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 32
    Açıklama
    Filter by order status (for example PAID, DELIVERED, COMPLETED).
  • q
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 80
    Açıklama
    Search reference or related text.
  • sort
    İçinde
    query
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    newest (default)
    Açıklama
    newest | oldest | total_high | total_low.
  • Response: { orders: Order[], total: number }.
GET/api/v1/orders/:uid
orders:read

Bir siparişi al

Satır öğeleri ile siparişi döner. Genel sipariş uid'sini kullanın.

İstek

  • uid
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    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

Teslim edildi olarak işaretle

Manuel yerine getirme. COMPLEX satırları gerekli olduğunda tamamen eklenmelidir. order.delivered yayar.

İstek

  • uid
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Order.uid.
  • evidence
    İçinde
    body
    Tür
    string[]
    Gerekli
    Opsiyonel
    Limitler
    HTTPS, max 10
    Açıklama
    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.

Barındırılan ödeme

Herhangi bir onaylı partner mağaza veya arka uç, alıcıları RMT.GG ödeme sayfasına yönlendirebilir. Biz kayıtlı satıcı olarak kalıyoruz ve kilitli tutarın %4'ünü alıyoruz.

Beyaz liste ve yerine getirme

Ayarlar altında, Barındırılan ödeme sayfasına gidin, ardından burada bir API anahtarı ve JSON webhook oluşturun. Ödeme sonrası checkout.completed bildirimi gönderiyoruz. Teslimat değerleri RMT.GG onayında kalır; satıcı GET veya webhook'larda yer almaz.

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

Barındırılan ödeme oturumu oluştur

Alıcıları kilitli bir RMT.GG ödeme sayfasına yönlendirin. Bir ürün: miktar ve itemName. Sepet: her satırda isim ve miktar ile items[]. Para birimi varsayılan olarak USD'dir. Ödeme sonrası alıcı, teslimat alanlarını kopyalamak için RMT.GG'de kalır. returnUrl, dükkana devam eder; teslimat yoksa kısa bir geri sayımdan sonra onları geri gönderiyoruz. Miktar, uzunluklar ve diğer sınırlar Limits sütunundadır.

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

İstek

  • amount
    İçinde
    body
    Tür
    number
    Gerekli
    Koşullu
    Limitler
    > 0, max 1,000,000
    Açıklama
    What the buyer pays. Required for a single item. With items[], omit it or send the line sum. Mismatch: 400 AMOUNT_MISMATCH.
  • currency
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Default USD
    Açıklama
    ISO 4217 code such as USD or EUR.
  • itemName
    İçinde
    body
    Tür
    string
    Gerekli
    Koşullu
    Limitler
    Max 120
    Açıklama
    Pay page heading. Required for a single item. Alias: title. With items[], defaults to the first line name. Missing: 400 ITEM_NAME_REQUIRED.
  • title
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Alias of itemName. If both are sent, itemName wins.
  • description
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 200
    Açıklama
    Copy under the heading. If omitted, the heading is reused.
  • imageUrl
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    HTTPS, max 2048
    Açıklama
    Product image, or fallback for lines without imageUrl. Invalid: 400 INVALID_IMAGE_URL.
  • items
    İçinde
    body
    Tür
    object[]
    Gerekli
    Koşullu
    Limitler
    1-20 lines, JSON max 48,000
    Açıklama
    Locked cart. Required when amount is omitted. Buyers cannot change lines. Empty: 400 INVALID_ITEMS.
  • items[].name
    İçinde
    body
    Tür
    string
    Gerekli
    gerekli
    Limitler
    Max 120
    Açıklama
    Line title. Alias: title.
  • items[].title
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Açıklama
    Alias of items[].name. If both are sent, name wins.
  • items[].description
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 200
    Açıklama
    Line copy under the name.
  • items[].amount
    İçinde
    body
    Tür
    number
    Gerekli
    gerekli
    Limitler
    > 0, max 1,000,000
    Açıklama
    Unit price. Session total is sum(amount * quantity).
  • items[].quantity
    İçinde
    body
    Tür
    number
    Gerekli
    Opsiyonel
    Limitler
    1-99, default 1
    Açıklama
    Locked on the pay page.
  • items[].imageUrl
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    HTTPS, max 2048
    Açıklama
    Line image. Falls back to top-level imageUrl.
  • items[].delivery
    İçinde
    body
    Tür
    object[]
    Gerekli
    Opsiyonel
    Limitler
    Max 16 fields
    Açıklama
    Shown after payment on RMT.GG. Seller GET and webhooks omit values.
  • items[].delivery[].name
    İçinde
    body
    Tür
    string
    Gerekli
    gerekli
    Limitler
    Max 80
    Açıklama
    Field label, for example Code or Password.
  • items[].delivery[].type
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    text, password, textarea
    Açıklama
    password is blurred until the buyer reveals it. Default text.
  • items[].delivery[].value
    İçinde
    body
    Tür
    string
    Gerekli
    gerekli
    Limitler
    Max 2048
    Açıklama
    Field value. Numbers are stored as strings. Empty: 400 INVALID_DELIVERY.
  • email
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Invalid values ignored
    Açıklama
    Prefills the pay page. The buyer still confirms email before paying.
  • returnUrl
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    HTTPS, max 2048
    Açıklama
    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
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    HTTPS, max 2048
    Açıklama
    Redirect if the buyer cancels or the session expires. If omitted, they stay on the pay page.
  • invoiceId
    İçinde
    body
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 128
    Açıklama
    Your shop id. Same payload returns the existing session. A different payload: 409 INVOICE_CONFLICT.
  • categorySlug
    İçinde
    body
    Tür
    string
    Gerekli
    Koşullu
    Limitler
    With offering, or omit both
    Açıklama
    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
    İçinde
    body
    Tür
    string
    Gerekli
    Koşullu
    Limitler
    With categorySlug, or omit both
    Açıklama
    Catalog offering such as Mods. Mapped to labels like Games · Add-ons.
  • metadata
    İçinde
    body
    Tür
    object
    Gerekli
    Opsiyonel
    Limitler
    Object, max 4096 chars
    Açıklama
    Stored on the session. Not returned on seller GET.
  • Idempotency-Key
    İçinde
    header
    Tür
    string
    Gerekli
    Opsiyonel
    Limitler
    Max 128
    Açıklama
    Replay header. Same key and payload returns the existing session. A different payload: 409 IDEMPOTENCY_CONFLICT.

Yanıt

  • uid
    İçinde
    response
    Tür
    string
    Açıklama
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    İçinde
    response
    Tür
    string
    Açıklama
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    İçinde
    response
    Tür
    boolean
    Açıklama
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    İçinde
    response
    Tür
    number
    Açıklama
    Locked buyer total in major units.
  • currency
    İçinde
    response
    Tür
    string
    Açıklama
    ISO currency code stored on the session (for example USD).
  • itemName
    İçinde
    response
    Tür
    string | null
    Açıklama
    Pay page heading.
  • description
    İçinde
    response
    Tür
    string
    Açıklama
    Longer copy under the heading.
  • email
    İçinde
    response
    Tür
    string | null
    Açıklama
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    İçinde
    response
    Tür
    string | null
    Açıklama
    Buyer locale when known. Not a create-session field.
  • returnUrl
    İçinde
    response
    Tür
    string | null
    Açıklama
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    İçinde
    response
    Tür
    string | null
    Açıklama
    Cancel/expiry redirect, or null.
  • invoiceId
    İçinde
    response
    Tür
    string | null
    Açıklama
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    İçinde
    response
    Tür
    string | null
    Açıklama
    Same as invoiceId (legacy alias).
  • source
    İçinde
    response
    Tür
    string
    Açıklama
    How the session was created. API sessions are "api".
  • expiresAt
    İçinde
    response
    Tür
    string
    Açıklama
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    İçinde
    response
    Tür
    string
    Açıklama
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    İçinde
    response
    Tür
    string | null
    Açıklama
    Marketplace order uid after payment. null until the session is paid.
  • items
    İçinde
    response
    Tür
    object[]
    Açıklama
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • hosted_url
    İçinde
    response
    Tür
    string
    Açıklama
    Pay page URL. Send the buyer here. Same target as hostedUrl.
  • expires_at
    İçinde
    response
    Tür
    string
    Açıklama
    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

Barındırılan ödeme oturumunu al

Oluşturduğunuz oturumu döndürür. checkout.completed gecikirse bunu kullanın. paid yalnızca durum paid olduğunda doğrudur. öğeler asla teslimat değerlerini içermez.

İstek

  • uid
    İçinde
    path
    Tür
    string
    Gerekli
    gerekli
    Açıklama
    Session uid returned at create time.

Yanıt

  • uid
    İçinde
    response
    Tür
    string
    Açıklama
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    İçinde
    response
    Tür
    string
    Açıklama
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    İçinde
    response
    Tür
    boolean
    Açıklama
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    İçinde
    response
    Tür
    number
    Açıklama
    Locked buyer total in major units.
  • currency
    İçinde
    response
    Tür
    string
    Açıklama
    ISO currency code stored on the session (for example USD).
  • itemName
    İçinde
    response
    Tür
    string | null
    Açıklama
    Pay page heading.
  • description
    İçinde
    response
    Tür
    string
    Açıklama
    Longer copy under the heading.
  • email
    İçinde
    response
    Tür
    string | null
    Açıklama
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    İçinde
    response
    Tür
    string | null
    Açıklama
    Buyer locale when known. Not a create-session field.
  • returnUrl
    İçinde
    response
    Tür
    string | null
    Açıklama
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    İçinde
    response
    Tür
    string | null
    Açıklama
    Cancel/expiry redirect, or null.
  • invoiceId
    İçinde
    response
    Tür
    string | null
    Açıklama
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    İçinde
    response
    Tür
    string | null
    Açıklama
    Same as invoiceId (legacy alias).
  • source
    İçinde
    response
    Tür
    string
    Açıklama
    How the session was created. API sessions are "api".
  • expiresAt
    İçinde
    response
    Tür
    string
    Açıklama
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    İçinde
    response
    Tür
    string
    Açıklama
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    İçinde
    response
    Tür
    string | null
    Açıklama
    Marketplace order uid after payment. null until the session is paid.
  • items
    İçinde
    response
    Tür
    object[]
    Açıklama
    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

Fatura ID'si ile barındırılan bir ödeme oturumunu sorgula

uid ile GET'teki aynı oturum nesnesi. Oluştururken gönderdiğiniz invoiceId'yi geçin. Eksik: 400 INVOICE_ID_REQUIRED. Bilinmeyen: 404 NOT_FOUND.

İstek

  • invoiceId
    İçinde
    query
    Tür
    string
    Gerekli
    gerekli
    Limitler
    Max 128
    Açıklama
    invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID.

Yanıt

  • uid
    İçinde
    response
    Tür
    string
    Açıklama
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    İçinde
    response
    Tür
    string
    Açıklama
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    İçinde
    response
    Tür
    boolean
    Açıklama
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    İçinde
    response
    Tür
    number
    Açıklama
    Locked buyer total in major units.
  • currency
    İçinde
    response
    Tür
    string
    Açıklama
    ISO currency code stored on the session (for example USD).
  • itemName
    İçinde
    response
    Tür
    string | null
    Açıklama
    Pay page heading.
  • description
    İçinde
    response
    Tür
    string
    Açıklama
    Longer copy under the heading.
  • email
    İçinde
    response
    Tür
    string | null
    Açıklama
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    İçinde
    response
    Tür
    string | null
    Açıklama
    Buyer locale when known. Not a create-session field.
  • returnUrl
    İçinde
    response
    Tür
    string | null
    Açıklama
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    İçinde
    response
    Tür
    string | null
    Açıklama
    Cancel/expiry redirect, or null.
  • invoiceId
    İçinde
    response
    Tür
    string | null
    Açıklama
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    İçinde
    response
    Tür
    string | null
    Açıklama
    Same as invoiceId (legacy alias).
  • source
    İçinde
    response
    Tür
    string
    Açıklama
    How the session was created. API sessions are "api".
  • expiresAt
    İçinde
    response
    Tür
    string
    Açıklama
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    İçinde
    response
    Tür
    string
    Açıklama
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    İçinde
    response
    Tür
    string | null
    Açıklama
    Marketplace order uid after payment. null until the session is paid.
  • items
    İçinde
    response
    Tür
    object[]
    Açıklama
    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.

Dışa dönük webhook'lar

Ayarlar → Geliştirici'de HTTPS uç noktalarını (veya Discord webhook'larını) yapılandırın. Abone olunan olaylar tetiklendiğinde RMT POST yapar.

order.paid
order.delivered
order.completed
order.refunded
order.disputed
offer.published
offer.updated
checkout.completed
checkout.canceled
checkout.refunded
  • JSON formatı, id, type, created ve data ile yapılandırılmış bir zarf gönderir.
  • Discord formatı, sipariş veya teklif bağlantıları ile zengin gömülü içerikler gönderir.
  • İsteğe bağlı imzalama, X-RMT-Timestamp ve X-RMT-Signature kullanır (rezerv ile aynı şemayı takip eder).
  • Teslimat geçmişi, her uç noktanın altında görünür, böylece hataları yeniden deneyebilirsiniz. Uç noktalar, tekrar eden hatalardan sonra otomatik olarak duraklatılır.
  • Barındırılan ödeme sayfası checkout.completed, checkout.canceled ve checkout.refunded bildirimlerini data.checkout ile gönderir. Teslimat değerleri hariç tutulur. Pazar yeri satışları order.paid ve diğer order.* olaylarını saklar.

JSON teslimat zarfı

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 */ ]
    }
  }
}

İmzalı teslimat başlıkları

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

ödeme.tamamlandı yükü

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 yükü

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"
        }
      ]
    }
  }
}

Webhook imzalarını doğrula

Bir imzalama gizli anahtarı ayarlandığında, timestamp + '.' + rawBody üzerinde HMAC-SHA256 hesaplayın ve v1= sonrası hex ile karşılaştırın.

Gizli anahtar RMT'de kalır. Her imzalı POST, X-RMT-Timestamp (Unix saniyeleri) ve X-RMT-Signature (v1= artı hex) içerir. String timestamp + '.' + rawBody üzerinde gizli anahtarınızı kullanarak HMAC-SHA256 hesaplayın, ardından v1= sonrasındaki hex ile karşılaştırın. 5 dakikadan eski zaman damgalarını reddedin.

  • Ham gövde baytlarını tam olarak alındığı gibi okuyun. JSON'u ayrıştırmayın ve hash'lemeden önce yeniden serileştirmeyin.
  • Zaman damgası ön eki olarak X-RMT-Timestamp başlık değerini kullanın (aynı dize, yeniden biçimlendirilmemiş).
  • Zaman güvenli eşitlik kontrolü ile karşılaştırın. Bir gizli anahtar yapılandırıldığında eksik veya uyumsuz imzaları olan istekleri reddedin.
  • Tekrarı sınırlamak için 5 dakikadan eski zaman damgalarını reddedin. Aynı şema reserve.item ve outbound order veya checkout olayları için de geçerlidir.

TypeScript doğrulaması (zaman güvenli karşılaştırma ve 5 dakikalık tekrar penceresi)

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 webhook işleyici

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;
  }
}

Webhook'ları rezerve et (envanter yenileme)

COMPLEX (benzersiz birim) listelemeleri için, RMT, yerel stok azaldığında ödeme sonrası bir sonraki lisansı, hesabı veya anahtarı oluşturmak için HTTPS uç noktanıza POST yapabilir.

Ödeme güvenli hatalar

Eğer uç noktanız zaman aşımına uğrarsa veya geçersiz veri dönerse, sipariş PAID olarak kalır. Alıcıya ücretlendirilir; siparişte bir hata görürsünüz ve rezervi yeniden deneyebilir veya anahtarları manuel olarak ekleyebilirsiniz.

Nasıl kurulur

  1. Öğe alanları ile KAPSAMLI bir teklif oluşturun (örneğin Lisans).
  2. Öğeler adımında, talep üzerine envanter uç noktasını etkinleştirin ve genel HTTPS URL'nizi yapıştırın.
  3. RMT'nin her çağrıda X-RMT-Timestamp ve X-RMT-Signature göndermesi için isteğe bağlı olarak bir imza gizli anahtarı ayarlayın.
  4. Test çalıştırın (veya örnek JSON yapıştırın), yanıt yollarını öğe alanlarına eşleyin, ardından Kaydet butonuna tıklayın.
  5. İlanı yayınlayın. Alıcılar boş yerel stokla satın alabilir; anahtarlar ödeme sonrası oluşturulur.
  • Yerel stok her zaman tercih edilir; webhook yalnızca eksik kısmı doldurur.
  • Teklif düzenleyicisinin Öğeler adımında, teklif düzeyinde varsayılanı yapılandırın veya fiyatlandırma seçeneğine göre geçersiz kılın.
  • Sadece HTTPS. İsteğe bağlı HMAC imzalama, dışa dönük webhook'larla eşleşir (X-RMT-Event: reserve.item).
  • Düzenleyicide test yapmak dryRun: true gönderir. Sipariş sayfasında, uç noktanızı düzelttikten sonra Rezervi Yenile'yi kullanın.

Kanonik POST gövdesi (kısaltılmış)

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
}

İstek başlıkları (bir imza gizli anahtarı ayarlandığında)

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

Kolaylık yanıtı

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

Eşlenmiş JSON alanları (yanıtMap yolları gibi $.license)

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

İmza gizli anahtarını nasıl doğrularsınız

Teklifte bir gizli anahtar ayarladıysanız, her reserve POST imzalanır. HMAC-SHA256(secret, timestamp + '.' + rawBody) hesaplayın ve v1= ön ekini kaldırdıktan sonra X-RMT-Signature ile karşılaştırın. Gizli anahtar istekte asla yer almaz.

Tam doğrulama örneğine bakın

TypeScript rezervasyon işleyici (doğrula, ardından girişleri döndür)

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 }],
  });
}

Ödeme öncesinde rezerv çağrısı yapmayın

RMT, yalnızca ödeme başarılı olduktan sonra uç noktanıza çağrı yapar, bu nedenle terkedilmiş alışverişler lisansları yakmaz.

Hatalar ve hız limitleri

Hatalar JSON { error, code? } döner. Açık API trafiği, her API anahtarı için dakikada 300 istekle sınırlıdır.

  • API_KEY_REQUIRED
    401

    Authorization veya X-Api-Key başlığı eksik.

  • API_KEY_INVALID
    401

    Anahtar bilinmiyor, iptal edilmiş, süresi dolmuş veya geliştirici erişimi askıya alınmış.

  • SCOPE_MISSING
    403

    Anahtar, uç noktanın gerektirdiği kapsamı taşımıyor.

  • RATE_LIMITED
    429

    Çok fazla istek. Retry-After ve X-RateLimit-Reset'e saygı gösterin.

  • CHECKOUT_PARTNER_NOT_APPROVED
    403

    Bu satıcı, barındırılan ödeme için onaylanmamıştır.

  • INVALID_JSON
    400

    İstek gövdesi JSON olmalıdır.

  • INVALID_AMOUNT
    400

    miktar 0'dan büyük ve en fazla 1.000.000 olmalıdır.

  • UNSUPPORTED_CURRENCY
    400

    para birimi desteklenen bir ISO kodu değildir.

  • INVALID_RETURN_URL
    400

    returnUrl ve cancelUrl https olmalıdır (http://localhost yerel mağazalar için kabul edilir).

  • INVALID_PSP_CATEGORY
    400

    categorySlug ve offering birlikte gönderilmeli ve bir katalog çiftini eşleştirmelidir.

  • INVALID_INVOICE_ID
    400

    invoiceId 128 karakterden uzun.

  • INVOICE_ID_REQUIRED
    400

    GET /checkout/sessions, invoiceId'yi sorgu parametresi olarak gerektirir.

  • INVALID_IDEMPOTENCY_KEY
    400

    Idempotency-Key 128 karakterden uzun.

  • INVALID_METADATA
    400

    metadata bir JSON nesnesi olmalıdır, dizi veya ilkel değil.

  • METADATA_TOO_LARGE
    400

    Serileştirilmiş metadata 4096 karakterden büyük.

  • IDEMPOTENCY_CONFLICT
    409

    Idempotency-Key farklı bir miktar, para birimi veya öğe ile yeniden kullanıldı.

  • INVOICE_CONFLICT
    409

    invoiceId farklı bir miktar, para birimi veya öğe ile yeniden kullanıldı.

  • INVALID_IMAGE_URL
    400

    imageUrl https URL olmalıdır.

  • ITEM_NAME_REQUIRED
    400

    itemName (veya başlık) belirtilmediğinde gereklidir.

  • INVALID_ITEMS
    400

    items, kilitli satır öğelerinin boş olmayan bir dizisi olmalıdır (maks 20). Her satırın ismi ve miktarı olmalıdır.

  • TOO_MANY_ITEMS
    400

    items 20'den fazla satır içeremez.

  • AMOUNT_MISMATCH
    400

    miktar, her satır miktarının miktar ile çarpımına eşit olmalıdır.

  • INVALID_DELIVERY
    400

    teslimat alanları geçersiz. Her alanın bir ismi (maks 80) ve değeri (maks 2048) olmalıdır. tür metin, şifre veya textarea olmalıdır (varsayılan metin). Her satırda en fazla 16 alan olabilir.

  • ITEMS_TOO_LARGE
    400

    Serileştirilmiş öğeler JSON'u 48.000 karakterden büyüktür.

  • NOT_FOUND
    404

    Bu satıcı için o uid veya invoiceId ile eşleşen bir barındırılan ödeme oturumu yok.

  • RESERVE_FAILED
    400

    Rezerv webhook'u zaman aşımına uğradı, geçersiz veri döndü veya gerekli alanları atladı.

  • OPTION_AMBIGUOUS
    409

    Birden fazla fiyatlandırma seçeneği bu adı karşılıyor. GET stokundan optionId'yi geçin.

  • OPTION_NOT_FOUND
    404

    Bu listelemede o id veya ad ile eşleşen fiyatlandırma seçeneği yok.

  • OPTION_REQUIRED
    400

    Bu listelemede birden fazla fiyatlandırma seçeneği var. Seçenek veya optionId'yi geçin.

  • UNKNOWN_FIELD
    400

    Bir alan adı bu listelemenin teslimat şemasına uymuyor.

  • FIELD_MAPPING_AMBIGUOUS
    400

    Sütunları veya anahtarları teslimat alanlarına eşleştiremedik. Başlıkları gönderin veya alan adıyla anahtarlanmış öğe nesnelerini kullanın.

  • STOCK_MODE_MISMATCH
    400

    O yük, seçeneğin stok moduyla (miktar vs kaydedilmiş öğeler) eşleşmiyor.

  • IMPORT_TOO_LARGE
    400

    Bir yeniden stoklama isteği, her seçenek için en fazla 1.000 kaydedilmiş öğe içe aktarabilir.

  • OPTION_ITEM_CAPACITY
    400

    Bu fiyatlandırma seçeneği zaten 5.000 satılmamış kaydedilmiş öğe maksimumuna ulaştı.

  • DUPLICATE_ITEMS
    409

    onDuplicate=error ve en az bir öğe zaten bu seçenekte mevcut.

  • UNLIMITED_STOCK
    400

    Bu seçenekte sınırsız miktar var. Önce sonlu bir sayıya geçmek için set kullanın.

  • INSUFFICIENT_STOCK
    400

    Kaldırmak için yeterli miktar stoku yok.

  • STOCK_HELD_IN_CHECKOUT
    400

    Ödeme aşamasında rezerve edilen birimlerin altına miktarı düşüremezsiniz.

  • INVALID_RESTOCK
    400

    Yeniden stoklama gövdesinde gerekli bir eylem eksik veya bir seçenekte add/items birleştirilmiş.

429 ile başa çıkın

Retry-After saniyelerini kullanarak geri çekilin. Limitleri aşmak için anahtarları döndürmeyin; limit her anahtar için ve tüm satıcılar için sabittir.

Başarılı yanıtlar X-RateLimit-Limit, X-RateLimit-Remaining ve X-RateLimit-Reset içerir.

Otomatikleştirmeye hazır mısınız?

Geliştirici ayarlarında bir anahtar oluşturun ve Bildirimler altında Discord veya Telegram'ı bağlayın.