RMT.GG/Documentação para desenvolvedores vendedores
v1

API do Vendedor

Automatize listagens, cumpra vendas e transmita eventos de pedidos. Inclui webhooks de saída e endpoints de reserva para reabastecimento de inventário sob demanda após o pagamento.

API REST Aberta

Autenticação Bearer /api/v1 para ofertas e pedidos, com cabeçalhos de descoberta e limite de taxa.

Webhooks de saída

Entregas HTTPS (ou Discord) assinadas para eventos do ciclo de vida de pedidos e ofertas.

Reservar / reabastecer

Crie estoque COMPLEXO a partir do seu servidor após o pagamento quando o inventário local estiver baixo.

O que você pode construir

A API Aberta do Vendedor é para vendedores que desejam alertas do Discord, sincronização de estoque, automação estilo Zapier ou um back office personalizado em cima do RMT.GG.

  • Gerenciar ofertas
    Crie rascunhos, atualize campos seguros, publique e arquive via /api/v1/offers.
  • Cumprir vendas
    Liste e inspecione pedidos de vendedores, depois marque-os como entregues com URLs de evidência opcionais.
  • Fique dentro do limite
    Cada chave tem um limite de 300 requisições por minuto. As respostas incluem cabeçalhos X-RateLimit-*.
  • Reaja em tempo real
    Inscreva-se em eventos de pedidos e ofertas, ou reabasteça o inventário COMPLEXO com webhooks de reserva.
  • Receba pagamentos da sua loja
    Parceiros aprovados podem enviar compradores de uma loja externa para o checkout hospedado, e depois cumprir o pedido.pago.

Início rápido

Crie uma chave de API nas configurações de Desenvolvedor e, em seguida, chame a descoberta para imprimir o catálogo ao vivo.

  1. 1Abra Configurações → Desenvolvedor (sem etapa separada de ativação).
  2. 2Crie uma chave de API e copie a chave secreta uma vez (rmt_sk_live_…). Armazene-a no seu gerenciador de segredos.
  3. 3Chame GET /api/v1 com Authorization: Bearer para confirmar escopos, cotas e operações.
GET/api/v1

Documento de descoberta

Retorna escopos, cotas, eventos de webhook e o catálogo completo de operações. Qualquer chave de API válida funciona.

Autenticação

Envie sua chave secreta ao vivo em cada requisição /api/v1. Prefira apenas HTTPS. Nunca insira chaves em clientes públicos ou pacotes de navegador.

Cabeçalho preferido

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

Cabeçalho alternativo

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

Cliente TypeScript reutilizável (autenticação Bearer, erros tipados, 429 retry)

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

Rotacione em caso de vazamento

Se uma chave vazar, revogue-a nas configurações de Desenvolvedor e crie uma nova. Atualize sua automação antes de revogar se você estiver ao vivo.

Escopos

Cada chave de API possui escopos que controlam os endpoints. Escopo ausente retorna 403 SCOPE_MISSING.

offers:read
offers:write
orders:read
orders:write
webhooks:manage
checkout:write
  • offers:read: Listar e obter suas ofertas.
  • offers:write: Criar, atualizar, publicar e excluir ofertas.
  • orders:read: Listar e obter pedidos de vendedores.
  • orders:write: Marcar pedidos como entregues.
  • webhooks:manage: Reservado para gerenciamento futuro de webhooks da Open API. Configure o Discord/Telegram em Notificações e webhooks JSON nas configurações de Desenvolvedor hoje.
  • checkout:write: Criar e ler sessões de checkout hospedadas. Requer checkout de parceiro aprovado pelo admin.

Escopos padrão da chave

Novas chaves recebem offers:read, offers:write, orders:read e orders:write. CRUD de webhook de saída permanece na UI de Configurações (autenticação de sessão).

API de Ofertas

Identificadores de oferta aceitam o slug da URL pública ou id numérico. Respostas omitem id interno e sellerId.

O que o PATCH ainda não pode mudar

Linhas de estoque, preços de opções, mídia e atributos são gerenciados no editor de vendedores (ou futuros endpoints), não via PATCH hoje.

GET/api/v1/offers
offers:read

Liste suas ofertas

Filtre com archive=active (padrão), archived ou all.

Requisição

  • archive
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Descrição
    One of "active" (default), "archived", or "all".
  • Response: { offers: Offer[], total: number }. Numeric id and sellerId are omitted.
POST/api/v1/offers
offers:write

Criar uma oferta de rascunho

Cria um rascunho vazio pertencente ao vendedor autenticado. Nenhum corpo é necessário.

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

Obter uma oferta

Carrega pelo slug da URL pública ou id numérico. Relações (opções) podem ser incluídas; itens de estoque não são.

Requisição

  • urlOrId
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    Offer.url slug or Offer.id.
  • Returns relations (options, etc.) when available; items are not included.
PATCH/api/v1/offers/:urlOrId
offers:write

Atualizar campos da oferta

Patch um subconjunto seguro de campos de listagem. Emite offer.updated quando webhooks de saída estão configurados.

Requisição

  • urlOrId
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    Offer.url slug or Offer.id.
  • title
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    Listing title.
  • description
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    Listing description.
  • visibility
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    PUBLIC | PRIVATE | UNPUBLISHED.
  • categoryId
    Em
    body
    Tipo
    number
    Necessário
    Opcional
    Descrição
    Catalog category id.
  • offeringId
    Em
    body
    Tipo
    number
    Necessário
    Opcional
    Descrição
    Catalog offering id.
  • thumbnail
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    Thumbnail URL or asset reference.
  • offerType
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    Offer type string used by the listing.
  • listingMode
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    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

Excluir ou arquivar

Mesmas regras de exclusão/arquivamento que a UI do vendedor.

Requisição

  • urlOrId
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    Offer.url slug or Offer.id.
  • Response: { ok: true }.
POST/api/v1/offers/:urlOrId/publish
offers:write

Publicar uma oferta

Publica um rascunho (ou altera a visibilidade). Falha com 400 se campos obrigatórios da listagem estiverem incompletos.

Requisição

  • urlOrId
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    Offer.url slug or Offer.id.
  • visibility
    Em
    body
    Tipo
    string
    Necessário
    Opcional
    Descrição
    Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED.
  • Response: { offer: Offer }.
  • Fails if the listing is incomplete for publish.

API de Estoque

Veja as quantidades disponíveis para compra por nível, combine os nomes dos campos de entrega com a listagem correta e, em seguida, reabasteça a quantidade ou as chaves e contas salvas.

Como funciona a correspondência

GET /api/v1/stock?fields=username,password encontra listagens cujo esquema possui esses campos. Reabasteça com nomes de opções (ou optionId) e nomes de campos. Você não precisa de ids de campo internos. As respostas nunca incluem valores de credenciais.

GET/api/v1/stock
offers:read

Listar estoque em suas listagens

Retorna contagens por nível e nomes dos campos de entrega para que você possa combinar chaves e contas com a oferta correta. Filtre com q, fields, stockMode e lowStock. Nunca retorna valores de credenciais.

Requisição

  • q
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Limites
    Max 80
    Descrição
    Filter by listing title or url slug.
  • fields
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Descrição
    Comma-separated delivery field names. The listing must have all of them (Username,Password). Names match case-insensitively.
  • stockMode
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Descrição
    QUANTITY or COMPLEX. Listing must have at least one option in that mode.
  • lowStock
    Em
    query
    Tipo
    number
    Necessário
    Opcional
    Descrição
    Keep listings that have a finite tier with available less than or equal to this number.
  • archive
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Descrição
    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

Obter estoque para uma listagem

Mesma estrutura de StockOffer que o índice, para uma url ou id numérico. Apenas contagens.

Requisição

  • urlOrId
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    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

Reabastecer uma listagem

Níveis de quantidade: adicionar, remover ou definir. Níveis de itens salvos: objetos de itens por nome de campo, keys[] quando há um campo, ou texto delimitado. Vários níveis em uma chamada via options[]. dryRun pré-visualiza a correspondência. onDuplicate padrão é pular.

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

Requisição

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

Importar várias contas ou chaves

Guia

Use POST /api/v1/offers/:url/stock com items[] para contas ou keys[] para códigos de licença de campo único. Divida em 1.000 linhas por solicitação.

  1. 1OBTENHA a listagem. Use fields[] e stockMode para escolher itens, chaves ou adicionar.
  2. 2Salve contas como JSON ou CSV, indexadas pelo nome do campo. Salve chaves de licença uma por linha.
  3. 3Faça um teste primeiro. Verifique wouldImport, skippedDuplicates e matchedFields.
  4. 4POST o mesmo corpo novamente sem dryRun para gravar o estoque.

Escolha o payload que corresponde à oferta

Chame GET stock primeiro. Se fields[] tiver mais de um nome, envie objetos items indexados por esses nomes (Nome de Usuário, Senha, E-Mail). Se houver exatamente um campo, keys[] é suficiente. Listagens de quantidade usam add, não items.

1.000 linhas por solicitação. 5.000 itens não vendidos por nível. 300 solicitações por minuto. Duplicatas são puladas por padrão.

accounts.json (um objeto por conta)

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

contas.csv

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

keys.txt (uma chave de licença por linha)

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

Importação em lote TypeScript (1.000 linhas por solicitação)

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

O que esta API protege

Chaves precisam de offers:write, têm limite de taxa e só podem reabastecer suas próprias listagens. GET nunca retorna credenciais salvas. Respostas POST não ecoam Nome de Usuário, Senha ou valores de chave. Envie o corpo via HTTPS em produção e mantenha a chave da API em uma variável de ambiente.

No Windows, use curl.exe (não o alias curl). Coloque o JSON entre aspas para que o PowerShell não o divida.

API de Pedidos

Os pedidos estão vinculados à sua conta de vendedor. Os detalhes de cobrança do comprador podem ser ocultados sob as regras de privacidade do marketplace.

GET/api/v1/orders
orders:read

Liste pedidos de vendedores

Suporta limite, offset, status, q e sort (mais novo, mais antigo, total_alto, total_baixo).

Requisição

  • limit
    Em
    query
    Tipo
    number
    Necessário
    Opcional
    Limites
    1-100, default 20
    Descrição
    Page size.
  • offset
    Em
    query
    Tipo
    number
    Necessário
    Opcional
    Limites
    >= 0, default 0
    Descrição
    Skip this many rows.
  • status
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Limites
    Max 32
    Descrição
    Filter by order status (for example PAID, DELIVERED, COMPLETED).
  • q
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Limites
    Max 80
    Descrição
    Search reference or related text.
  • sort
    Em
    query
    Tipo
    string
    Necessário
    Opcional
    Limites
    newest (default)
    Descrição
    newest | oldest | total_high | total_low.
  • Response: { orders: Order[], total: number }.
GET/api/v1/orders/:uid
orders:read

Obter um pedido

Retorna o pedido com itens de linha. Use o uid público do pedido.

Requisição

  • uid
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    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

Marcar como entregue

Cumprimento manual. Linhas COMPLEX devem estar totalmente anexadas quando necessário. Emite order.delivered.

Requisição

  • uid
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    Order.uid.
  • evidence
    Em
    body
    Tipo
    string[]
    Necessário
    Opcional
    Limites
    HTTPS, max 10
    Descrição
    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.

Checkout hospedado

Qualquer loja parceira aprovada ou backend pode enviar compradores para uma página de pagamento do RMT.GG. Nós permanecemos como comerciante registrado e cobramos 4% do valor bloqueado.

Lista de permissão e cumprimento

Aplique em Configurações, Checkout hospedado, e então crie uma chave de API e um webhook JSON lá. Após o pagamento, emitimos checkout.completed. Os valores de entrega ficam na confirmação da RMT.GG; eles não estão no GET do vendedor ou nos webhooks.

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

Criar uma sessão de checkout hospedado

Envie os compradores para uma página de pagamento bloqueada do RMT.GG. Um item: quantidade e itemName. Carrinho: items[] com nome e quantidade em cada linha. A moeda padrão é USD. Após o pagamento, o comprador permanece no RMT.GG quando há campos de entrega para copiar. returnUrl continua para a loja; sem entrega, nós os enviamos de volta após uma contagem regressiva curta. Quantidade, comprimentos e outros limites estão na coluna Limites.

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

Requisição

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

Resposta

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

Obter uma sessão de checkout hospedado

Retorna a sessão que você criou. Use isso se checkout.completed estiver atrasado. paid é verdadeiro apenas quando o status está pago. itens nunca incluem valores de entrega.

Requisição

  • uid
    Em
    path
    Tipo
    string
    Necessário
    obrigatório
    Descrição
    Session uid returned at create time.

Resposta

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

Consultar uma sessão de checkout hospedada pelo ID da fatura

Mesmo objeto de sessão que GET por uid. Passe o invoiceId que você enviou na criação. Faltando: 400 INVOICE_ID_REQUIRED. Desconhecido: 404 NOT_FOUND.

Requisição

  • invoiceId
    Em
    query
    Tipo
    string
    Necessário
    obrigatório
    Limites
    Max 128
    Descrição
    invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID.

Resposta

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

Webhooks de saída

Configure endpoints HTTPS (ou webhooks do Discord) em Configurações → Desenvolvedor. O RMT POSTa quando eventos inscritos ocorrem.

order.paid
order.delivered
order.completed
order.refunded
order.disputed
offer.published
offer.updated
checkout.completed
checkout.canceled
checkout.refunded
  • O formato JSON envia um envelope estruturado com id, tipo, criado e dados.
  • O formato Discord envia embeds ricos com links de pedidos ou ofertas.
  • A assinatura opcional usa X-RMT-Timestamp e X-RMT-Signature (mesmo esquema que reserva).
  • O histórico de entregas aparece sob cada endpoint para que você possa tentar novamente em caso de falhas. Os endpoints pausam automaticamente após falhas repetidas.
  • O checkout hospedado envia checkout.completed, checkout.canceled e checkout.refunded com data.checkout. Os valores de entrega são omitidos. As vendas do marketplace mantêm order.paid e outros eventos order.*.

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

Cabeçalhos de entrega assinados

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

payload de checkout.concluído

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

Carga útil de 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"
        }
      ]
    }
  }
}

Verificar assinaturas de webhook

Quando uma chave secreta de assinatura está definida, calcule HMAC-SHA256 sobre timestamp + '.' + rawBody e compare com o hex após v1=.

O segredo permanece na RMT. Cada POST assinado inclui X-RMT-Timestamp (segundos Unix) e X-RMT-Signature (v1= mais hex). Calcule HMAC-SHA256 sobre a string timestamp + '.' + rawBody usando seu segredo, depois compare com o hex após v1=. Rejeite timestamps mais antigos que 5 minutos.

  • Leia os bytes do corpo bruto exatamente como recebidos. Não analise JSON e reserialize antes de fazer o hash.
  • Use o valor do cabeçalho X-RMT-Timestamp como o prefixo de timestamp (mesma string, não reformulada).
  • Compare com uma verificação de igualdade segura em termos de tempo. Rejeite solicitações com assinaturas ausentes ou incompatíveis quando um segredo estiver configurado.
  • Rejeite timestamps mais antigos que 5 minutos para limitar a repetição. O mesmo esquema se aplica a reserve.item e eventos de pedido ou checkout de saída.

Verificação TypeScript (comparação segura em tempo e janela de repetição de 5 minutos)

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

Manipulador de webhook TypeScript

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

Reservar webhooks (reabastecimento de inventário)

Para listagens COMPLEX (unidade única), o RMT pode POSTar seu endpoint HTTPS após o pagamento para criar a próxima licença, conta ou chave quando o estoque local estiver baixo.

Falhas seguras de pagamento

Se seu endpoint expirar ou retornar dados inválidos, o pedido permanece PAGO. O comprador é cobrado; você verá um erro no pedido e poderá tentar reservar novamente ou anexar chaves manualmente.

Como configurá-lo

  1. Crie uma oferta COMPLEXA com campos de item (por exemplo, Licença).
  2. Na etapa de Itens, ative o endpoint de inventário sob demanda e cole sua URL pública HTTPS.
  3. Opcionalmente, defina um segredo de assinatura para que o RMT envie X-RMT-Timestamp e X-RMT-Signature em cada chamada.
  4. Execute o Teste (ou cole um JSON de exemplo), mapeie os caminhos de resposta para os campos de item e, em seguida, Salve.
  5. Publique a listagem. Os compradores podem comprar com estoque local vazio; as chaves são criadas após o pagamento.
  • O estoque local é sempre preferido; o webhook preenche apenas a falta.
  • Configure um padrão de oferta, ou substitua por opção de preço, na etapa Itens do editor de ofertas.
  • Apenas HTTPS. A assinatura HMAC opcional combina com webhooks de saída (X-RMT-Event: reserve.item).
  • Testar no editor envia dryRun: true. Na página do pedido, use Tentar reservar novamente após corrigir seu endpoint.

Corpo POST canônico (truncado)

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
}

Cabeçalhos da solicitação (quando um segredo de assinatura está definido)

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

Resposta de conveniência

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

Campos JSON mapeados (com caminhos responseMap como $.license)

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

Como verificar o segredo de assinatura

Se você definiu um segredo na oferta, cada POST de reserva é assinado. Recalcule HMAC-SHA256(segredo, timestamp + '.' + corpoBruto) e compare com X-RMT-Signature após remover o prefixo v1=. O segredo em si nunca é incluído na solicitação.

Veja o exemplo completo de verificação

Manipulador de reserva TypeScript (verificar, depois retornar entradas)

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

Não chame reserva antes do pagamento

O RMT só chama seu endpoint após o pagamento ser bem-sucedido, então checkouts abandonados não consomem licenças.

Erros e limites de taxa

Erros retornam JSON { error, code? }. O tráfego da API Aberta é limitado a 300 requisições por minuto por chave de API.

  • API_KEY_REQUIRED
    401

    Cabeçalho Authorization ou X-Api-Key ausente.

  • API_KEY_INVALID
    401

    Chave desconhecida, revogada, expirada ou acesso de desenvolvedor suspenso.

  • SCOPE_MISSING
    403

    A chave não possui o escopo exigido pelo endpoint.

  • RATE_LIMITED
    429

    Muitas requisições. Respeite Retry-After e X-RateLimit-Reset.

  • CHECKOUT_PARTNER_NOT_APPROVED
    403

    Este vendedor não está aprovado para checkout hospedado.

  • INVALID_JSON
    400

    O corpo da solicitação deve ser JSON.

  • INVALID_AMOUNT
    400

    o valor deve ser maior que 0 e no máximo 1.000.000.

  • UNSUPPORTED_CURRENCY
    400

    a moeda não é um código ISO suportado.

  • INVALID_RETURN_URL
    400

    returnUrl e cancelUrl devem ser https (http://localhost é permitido para lojas locais).

  • INVALID_PSP_CATEGORY
    400

    categorySlug e offering devem ser enviados juntos e corresponder a um par de catálogo.

  • INVALID_INVOICE_ID
    400

    invoiceId é maior que 128 caracteres.

  • INVOICE_ID_REQUIRED
    400

    GET /checkout/sessions requer invoiceId como um parâmetro de consulta.

  • INVALID_IDEMPOTENCY_KEY
    400

    Idempotency-Key é maior que 128 caracteres.

  • INVALID_METADATA
    400

    metadata deve ser um objeto JSON, não um array ou primitivo.

  • METADATA_TOO_LARGE
    400

    A metadata serializada é maior que 4096 caracteres.

  • IDEMPOTENCY_CONFLICT
    409

    Idempotency-Key foi reutilizado com um valor, moeda ou item diferente.

  • INVOICE_CONFLICT
    409

    invoiceId foi reutilizado com um valor, moeda ou item diferente.

  • INVALID_IMAGE_URL
    400

    imageUrl deve ser uma URL https.

  • ITEM_NAME_REQUIRED
    400

    itemName (ou título) é obrigatório quando items é omitido.

  • INVALID_ITEMS
    400

    items deve ser um array não vazio de itens bloqueados (máx. 20). Cada linha precisa de nome e amount.

  • TOO_MANY_ITEMS
    400

    itens não podem conter mais de 20 linhas.

  • AMOUNT_MISMATCH
    400

    a quantidade deve ser igual à soma de cada valor da linha multiplicado pela quantidade.

  • INVALID_DELIVERY
    400

    Os campos de entrega são inválidos. Cada campo precisa de um nome (máx. 80) e valor (máx. 2048). type deve ser text, password ou textarea (padrão text). Máx. 16 campos por linha.

  • ITEMS_TOO_LARGE
    400

    O JSON dos itens serializados é maior que 48.000 caracteres.

  • NOT_FOUND
    404

    Nenhuma sessão de checkout hospedado corresponde a esse uid ou invoiceId para este vendedor.

  • RESERVE_FAILED
    400

    Webhook de reserva expirou, retornou dados inválidos ou faltaram campos obrigatórios.

  • OPTION_AMBIGUOUS
    409

    Mais de uma opção de preço corresponde a esse nome. Passe optionId do GET stock.

  • OPTION_NOT_FOUND
    404

    Nenhuma opção de preço corresponde a esse id ou nome nesta listagem.

  • OPTION_REQUIRED
    400

    Esta listagem tem várias opções de preço. Passe option ou optionId.

  • UNKNOWN_FIELD
    400

    Um nome de campo não corresponde ao esquema de entrega desta listagem.

  • FIELD_MAPPING_AMBIGUOUS
    400

    Não foi possível mapear colunas ou chaves para os campos de entrega. Envie cabeçalhos ou use objetos de itens indexados pelo nome do campo.

  • STOCK_MODE_MISMATCH
    400

    Esse payload não corresponde ao modo de estoque da opção (quantidade vs itens salvos).

  • IMPORT_TOO_LARGE
    400

    Uma solicitação de reabastecimento pode importar no máximo 1.000 itens salvos por opção.

  • OPTION_ITEM_CAPACITY
    400

    Esta opção de preço já possui o máximo de 5.000 itens salvos não vendidos.

  • DUPLICATE_ITEMS
    409

    onDuplicate=error e pelo menos um item já existe nesta opção.

  • UNLIMITED_STOCK
    400

    Esta opção tem quantidade ilimitada. Use set para mudar para uma contagem finita primeiro.

  • INSUFFICIENT_STOCK
    400

    Quantidade de estoque insuficiente para remover.

  • STOCK_HELD_IN_CHECKOUT
    400

    Não é possível reduzir a quantidade abaixo das unidades atualmente reservadas no checkout.

  • INVALID_RESTOCK
    400

    O corpo do reabastecimento está faltando uma ação obrigatória ou combina add/items em uma opção.

Lidar com 429

Aguarde usando Retry-After segundos. Não rotacione chaves para contornar limites; o limite é por chave e fixo para todos os vendedores.

Respostas bem-sucedidas incluem X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset.

Pronto para automatizar?

Crie uma chave nas configurações de Desenvolvedor e conecte o Discord ou Telegram em Notificações.