RMT.GG/Documentation développeur vendeur
v1

API Vendeur

Automatisez les annonces, réalisez des ventes et diffusez des événements de commande. Inclut des webhooks sortants et des points de terminaison de réserve pour le réapprovisionnement d'inventaire à la demande après paiement.

API REST Ouverte

Authentification par Bearer /api/v1 pour les offres et les commandes, avec des en-têtes de découverte et de limitation de taux.

Webhooks sortants

Livraisons HTTPS signées (ou Discord) pour les événements du cycle de vie des commandes et des offres.

Réserve / réapprovisionnement

Créez des stocks COMPLEX à partir de votre serveur après paiement lorsque l'inventaire local est insuffisant.

Ce que vous pouvez construire

L'API ouverte pour les vendeurs est destinée aux vendeurs qui souhaitent des alertes Discord, une synchronisation des stocks, une automatisation de style Zapier, ou un back office personnalisé sur RMT.GG.

  • Gérer les offres
    Créez des brouillons, mettez à jour des champs sécurisés, publiez et archivez via /api/v1/offers.
  • Réaliser des ventes
    Listez et inspectez les commandes des vendeurs, puis marquez-les comme livrées avec des URL de preuves optionnelles.
  • Rester sous la limite
    Chaque clé est limitée à 300 requêtes par minute. Les réponses incluent des en-têtes X-RateLimit-*.
  • Réagir en temps réel
    Abonnez-vous aux événements de commande et d'offre, ou réapprovisionnez l'inventaire COMPLEX avec des webhooks de réserve.
  • Acceptez les paiements de votre boutique
    Les partenaires approuvés peuvent rediriger les acheteurs d'une boutique externe vers le paiement hébergé, puis exécuter la commande.paid.

Démarrage rapide

Créez une clé API dans les paramètres Développeur, puis appelez la découverte pour imprimer le catalogue en direct.

  1. 1Ouvrez Paramètres → Développeur (pas d'étape d'activation séparée).
  2. 2Créez une clé API et copiez le secret une fois (rmt_sk_live_…). Conservez-le dans votre gestionnaire de secrets.
  3. 3Appelez GET /api/v1 avec Authorization: Bearer pour confirmer les scopes, les quotas et les opérations.
GET/api/v1

Document de découverte

Renvoie des scopes, des quotas, des événements de webhook et le catalogue complet des opérations. Toute clé API valide fonctionne.

Authentification

Envoyez votre clé secrète en direct à chaque requête /api/v1. Préférez uniquement HTTPS. Ne jamais intégrer de clés dans des clients publics ou des bundles de navigateur.

En-tête préféré

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

En-tête alternatif

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

Client TypeScript réutilisable (auth Bearer, erreurs typées, réessai 429)

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

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

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

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

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

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

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

Faire tourner en cas de fuite

Si une clé fuit, révoquez-la dans les paramètres Développeur et créez-en une nouvelle. Mettez à jour votre automatisation avant de révoquer si vous êtes en direct.

Scopes

Chaque clé API porte des scopes qui contrôlent les points de terminaison. Un scope manquant renvoie 403 SCOPE_MISSING.

offers:read
offers:write
orders:read
orders:write
webhooks:manage
checkout:write
  • offers:read: Lister et obtenir vos offres.
  • offers:write: Créer, mettre à jour, publier et supprimer des offres.
  • orders:read: Lister et obtenir les commandes des vendeurs.
  • orders:write: Marquer les commandes comme livrées.
  • webhooks:manage: Réservé à la gestion future des webhooks de l'Open API. Configurez Discord/Telegram dans les Notifications et les webhooks JSON dans les paramètres Développeur dès aujourd'hui.
  • checkout:write: Créer et lire des sessions de paiement hébergées. Nécessite un paiement partenaire approuvé par l'administrateur.

Scopes par défaut des clés

Les nouvelles clés reçoivent offers:read, offers:write, orders:read et orders:write. La gestion CRUD des webhooks sortants reste dans l'interface des paramètres (authentification de session).

API des Offres

Les identifiants d'offres acceptent le slug d'URL public ou l'ID numérique. Les réponses omettent l'ID interne et sellerId.

Ce que PATCH ne peut pas encore changer

Les lignes de stock, les prix des options, les médias et les attributs sont gérés dans l'éditeur de vendeur (ou futurs points de terminaison), pas via PATCH aujourd'hui.

GET/api/v1/offers
offers:read

Lister vos offres

Filtrer avec archive=active (par défaut), archivé ou tout.

Demande

  • archive
    Dans
    query
    Type
    string
    Requis
    Optionnel
    Description
    One of "active" (default), "archived", or "all".
  • Response: { offers: Offer[], total: number }. Numeric id and sellerId are omitted.
POST/api/v1/offers
offers:write

Créer une offre brouillon

Crée un brouillon vide appartenant au vendeur authentifié. Aucun corps requis.

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

Obtenir une offre

Charger par slug d'URL public ou ID numérique. Les relations (options) peuvent être incluses ; les articles de stock ne le sont pas.

Demande

  • urlOrId
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    Offer.url slug or Offer.id.
  • Returns relations (options, etc.) when available; items are not included.
PATCH/api/v1/offers/:urlOrId
offers:write

Mettre à jour les champs de l'offre

Patch un sous-ensemble sécurisé des champs d'annonce. Émet offer.updated lorsque des webhooks sortants sont configurés.

Demande

  • urlOrId
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    Offer.url slug or Offer.id.
  • title
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    Listing title.
  • description
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    Listing description.
  • visibility
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    PUBLIC | PRIVATE | UNPUBLISHED.
  • categoryId
    Dans
    body
    Type
    number
    Requis
    Optionnel
    Description
    Catalog category id.
  • offeringId
    Dans
    body
    Type
    number
    Requis
    Optionnel
    Description
    Catalog offering id.
  • thumbnail
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    Thumbnail URL or asset reference.
  • offerType
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    Offer type string used by the listing.
  • listingMode
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    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

Supprimer ou archiver

Les mêmes règles de suppression/archivage que l'interface vendeur.

Demande

  • urlOrId
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    Offer.url slug or Offer.id.
  • Response: { ok: true }.
POST/api/v1/offers/:urlOrId/publish
offers:write

Publier une offre

Publie un brouillon (ou change la visibilité). Échoue avec 400 si les champs d'annonce requis sont incomplets.

Demande

  • urlOrId
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    Offer.url slug or Offer.id.
  • visibility
    Dans
    body
    Type
    string
    Requis
    Optionnel
    Description
    Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED.
  • Response: { offer: Offer }.
  • Fails if the listing is incomplete for publish.

API de Stock

Voir les quantités achetables par niveau, faire correspondre les noms des champs de livraison à la bonne annonce, puis réapprovisionner la quantité ou les clés et comptes sauvegardés.

Comment fonctionne la correspondance

GET /api/v1/stock?fields=username,password trouve des annonces dont le schéma a ces champs. Réapprovisionnez avec les noms d'options (ou optionId) et les noms de champs. Vous n'avez pas besoin d'identifiants de champ internes. Les réponses n'incluent jamais les valeurs d'identification.

GET/api/v1/stock
offers:read

Lister le stock de vos annonces

Retourne les quantités par niveau et les noms des champs de livraison afin que vous puissiez faire correspondre les clés et comptes à la bonne offre. Filtrez avec q, fields, stockMode et lowStock. Ne retourne jamais les valeurs d'identification.

Demande

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

Obtenir le stock pour une annonce

Même forme que StockOffer que l'index, pour une url ou un id numérique. Compte uniquement.

Demande

  • urlOrId
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    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

Réapprovisionner une annonce

Niveaux de quantité : ajouter, retirer ou définir. Niveaux d'articles sauvegardés : objets d'articles par nom de champ, keys[] lorsqu'il y a un champ, ou texte délimité. Plusieurs niveaux dans un seul appel via options[]. dryRun prévisualise la correspondance. onDuplicate par défaut à ignorer.

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

Demande

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

Importer plusieurs comptes ou clés

Guide

Utilisez POST /api/v1/offers/:url/stock avec items[] pour les comptes ou keys[] pour les codes de licence à champ unique. Limitez à 1 000 lignes par requête.

  1. 1OBTENEZ l'annonce. Utilisez fields[] et stockMode pour choisir des objets, des clés ou ajouter.
  2. 2Enregistrez les comptes au format JSON ou CSV en utilisant le nom du champ comme clé. Enregistrez les clés de licence une par ligne.
  3. 3Faites un essai d'abord. Vérifiez wouldImport, skippedDuplicates et matchedFields.
  4. 4POST le même corps à nouveau sans dryRun pour écrire le stock.

Choisissez la charge utile qui correspond à l'offre

Appelez d'abord GET stock. Si fields[] a plus d'un nom, envoyez des objets items indexés par ces noms (Nom d'utilisateur, Mot de passe, E-Mail). S'il y a exactement un champ, keys[] suffit. Les listes de quantité utilisent add, pas items.

1 000 lignes par demande. 5 000 objets non vendus par niveau. 300 demandes par minute. Les doublons sont ignorés par défaut.

accounts.json (un objet par compte)

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

comptes.csv

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

keys.txt (une clé de licence par ligne)

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

Importation par lot TypeScript (1 000 lignes par requête)

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

Ce que cette API protège

Les clés nécessitent offers:write, sont limitées en taux, et ne peuvent réapprovisionner que vos propres offres. GET ne renvoie jamais les identifiants enregistrés. Les réponses POST n'écho pas les valeurs de Nom d'utilisateur, Mot de passe ou clé. Envoyez le corps via HTTPS en production et gardez la clé API dans une variable d'environnement.

Sous Windows, utilisez curl.exe (pas l'alias curl). Entourez le -d JSON de guillemets pour que PowerShell ne le divise pas.

API des Commandes

Les commandes sont limitées à votre compte vendeur. Les détails de facturation de l'acheteur peuvent être masqués selon les règles de confidentialité du marché.

GET/api/v1/orders
orders:read

Lister les commandes des vendeurs

Prend en charge limit, offset, status, q et sort (nouveau, ancien, total_haut, total_bas).

Demande

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

Obtenir une commande

Renvoie la commande avec les lignes d'articles. Utilisez l'uid de commande public.

Demande

  • uid
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    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

Marquer comme livré

Exécution manuelle. Les lignes COMPLEX doivent être entièrement attachées lorsque requis. Émet order.delivered.

Demande

  • uid
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    Order.uid.
  • evidence
    Dans
    body
    Type
    string[]
    Requis
    Optionnel
    Limites
    HTTPS, max 10
    Description
    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.

Paiement hébergé

Tout partenaire approuvé peut envoyer des acheteurs vers une page de paiement RMT.GG. Nous restons le marchand enregistré et prenons 4 % du montant bloqué.

Liste blanche et exécution

Appliquez sous Paramètres, Paiement hébergé, puis créez une clé API et un webhook JSON là-bas. Après le paiement, nous émettons checkout.completed. Les valeurs de livraison restent sur la confirmation RMT.GG ; elles ne sont pas dans le GET du vendeur ou les webhooks.

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

Créer une session de paiement hébergé

Envoyez les acheteurs vers une page de paiement verrouillée de RMT.GG. Un article : montant et itemName. Panier : items[] avec nom et montant sur chaque ligne. La devise par défaut est l'USD. Après le paiement, l'acheteur reste sur RMT.GG lorsqu'il y a des champs de livraison à copier. returnUrl continue vers la boutique ; sans livraison, nous les renvoyons après un court compte à rebours. Montants, longueurs et autres limites se trouvent dans la colonne Limites.

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

Demande

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

Réponse

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

Obtenir une session de paiement hébergé

Renvoie la session que vous avez créée. Utilisez ceci si checkout.completed est retardé. paid est vrai uniquement lorsque le statut est payé. items n'inclut jamais les valeurs de livraison.

Demande

  • uid
    Dans
    path
    Type
    string
    Requis
    requis
    Description
    Session uid returned at create time.

Réponse

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

Rechercher une session de paiement hébergée par ID de facture

Même objet de session que GET par uid. Passez l'invoiceId que vous avez envoyé à la création. Manquant : 400 INVOICE_ID_REQUIRED. Inconnu : 404 NOT_FOUND.

Demande

  • invoiceId
    Dans
    query
    Type
    string
    Requis
    requis
    Limites
    Max 128
    Description
    invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID.

Réponse

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

Configurez des points de terminaison HTTPS (ou des webhooks Discord) dans Paramètres → Développeur. RMT POST lorsque les événements abonnés se déclenchent.

order.paid
order.delivered
order.completed
order.refunded
order.disputed
offer.published
offer.updated
checkout.completed
checkout.canceled
checkout.refunded
  • Le format JSON envoie une enveloppe structurée avec id, type, créé et données.
  • Le format Discord envoie des intégrations riches avec des liens de commande ou d'offre.
  • La signature optionnelle utilise X-RMT-Timestamp et X-RMT-Signature (même schéma que la réserve).
  • L'historique des livraisons apparaît sous chaque point de terminaison afin que vous puissiez réessayer les échecs. Les points de terminaison se mettent automatiquement en pause après des échecs répétés.
  • Le paiement hébergé envoie checkout.completed, checkout.canceled et checkout.refunded avec data.checkout. Les valeurs de livraison sont omises. Les ventes du marché conservent order.paid et d'autres événements order.*.

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

En-têtes de livraison signés

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

payload de commande terminée

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

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

Vérifier les signatures des webhooks

Lorsqu'un secret de signature est défini, calculez HMAC-SHA256 sur timestamp + '.' + rawBody et comparez avec l'hex après v1=.

Le secret reste sur RMT. Chaque POST signé inclut X-RMT-Timestamp (secondes Unix) et X-RMT-Signature (v1= plus hex). Calculez HMAC-SHA256 sur la chaîne timestamp + '.' + rawBody en utilisant votre secret, puis comparez avec l'hex après v1=. Rejetez les timestamps plus anciens que 5 minutes.

  • Lisez les octets du corps brut exactement tels que reçus. Ne parsez pas le JSON et ne le re-sérialisez pas avant le hachage.
  • Utilisez la valeur de l'en-tête X-RMT-Timestamp comme préfixe de timestamp (même chaîne, pas reformatée).
  • Comparez avec une vérification d'égalité sécurisée par le temps. Rejetez les demandes avec des signatures manquantes ou non correspondantes lorsqu'un secret est configuré.
  • Rejetez les timestamps plus anciens que 5 minutes pour limiter la répétition. Le même schéma s'applique aux événements reserve.item et aux commandes ou paiements sortants.

Vérification TypeScript (comparaison sécurisée par temps et fenêtre de répétition de 5 minutes)

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

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

Réserver des webhooks (réapprovisionnement d'inventaire)

Pour les annonces COMPLEX (unité unique), RMT peut POST votre point de terminaison HTTPS après paiement pour créer la prochaine licence, compte ou clé lorsque le stock local est insuffisant.

Échecs sécurisés par paiement

Si votre point de terminaison expire ou renvoie des données invalides, la commande reste PAYÉE. L'acheteur est facturé ; vous voyez une erreur sur la commande et pouvez réessayer de réserver ou attacher des clés manuellement.

Comment le configurer

  1. Créez une offre COMPLÈTE avec des champs d'objets (par exemple Licence).
  2. À l'étape des Objets, activez le point de terminaison d'inventaire à la demande et collez votre URL HTTPS publique.
  3. Définissez éventuellement un secret de signature afin que RMT envoie X-RMT-Timestamp et X-RMT-Signature à chaque appel.
  4. Exécutez le Test (ou collez un JSON d'exemple), mappez les chemins de réponse aux champs d'objets, puis Enregistrez.
  5. Publiez l'annonce. Les acheteurs peuvent acheter avec un stock local vide ; les clés sont créées après le paiement.
  • Le stock local est toujours préféré ; le webhook ne remplit que le manque.
  • Configurez un défaut au niveau de l'offre, ou remplacez-le par option de tarification, à l'étape Items de l'éditeur d'offres.
  • HTTPS uniquement. La signature HMAC optionnelle correspond aux webhooks sortants (X-RMT-Event: reserve.item).
  • Tester dans l'éditeur envoie dryRun: true. Sur la page de commande, utilisez Réessayer la réserve après avoir corrigé votre point de terminaison.

Corps POST canonique (tronqué)

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
}

En-têtes de requête (lorsqu'un secret de signature est défini)

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

Réponse de commodité

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

Champs JSON mappés (avec des chemins responseMap comme $.license)

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

Comment vérifier le secret de signature

Si vous avez défini un secret sur l'offre, chaque POST de réservation est signé. Recalculez HMAC-SHA256(secret, timestamp + '.' + rawBody) et comparez avec X-RMT-Signature après avoir supprimé le préfixe v1=. Le secret lui-même n'est jamais inclus dans la requête.

Voir l'exemple complet de vérification

Gestionnaire de réservation TypeScript (vérifiez, puis retournez les entrées)

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

Ne pas appeler la réserve avant le paiement

RMT n'appelle votre point de terminaison qu'après le succès du paiement, donc les abandons de panier ne consomment pas de licences.

Erreurs et limites de taux

Les erreurs renvoient JSON { error, code? }. Le trafic de l'API ouverte est limité à 300 requêtes par minute par clé API.

  • API_KEY_REQUIRED
    401

    En-tête Authorization ou X-Api-Key manquant.

  • API_KEY_INVALID
    401

    Clé inconnue, révoquée, expirée ou accès développeur suspendu.

  • SCOPE_MISSING
    403

    La clé manque le scope requis par le point de terminaison.

  • RATE_LIMITED
    429

    Trop de requêtes. Respectez Retry-After et X-RateLimit-Reset.

  • CHECKOUT_PARTNER_NOT_APPROVED
    403

    Ce vendeur n'est pas approuvé pour le paiement hébergé.

  • INVALID_JSON
    400

    Le corps de la requête doit être en JSON.

  • INVALID_AMOUNT
    400

    le montant doit être supérieur à 0 et au maximum 1 000 000.

  • UNSUPPORTED_CURRENCY
    400

    la devise n'est pas un code ISO pris en charge.

  • INVALID_RETURN_URL
    400

    returnUrl et cancelUrl doivent être en https (http://localhost est autorisé pour les boutiques locales).

  • INVALID_PSP_CATEGORY
    400

    categorySlug et offering doivent être envoyés ensemble et correspondre à une paire de catalogue.

  • INVALID_INVOICE_ID
    400

    invoiceId est plus long que 128 caractères.

  • INVOICE_ID_REQUIRED
    400

    GET /checkout/sessions nécessite invoiceId comme paramètre de requête.

  • INVALID_IDEMPOTENCY_KEY
    400

    Idempotency-Key est plus long que 128 caractères.

  • INVALID_METADATA
    400

    metadata doit être un objet JSON, pas un tableau ou une primitive.

  • METADATA_TOO_LARGE
    400

    Les métadonnées sérialisées sont plus grandes que 4096 caractères.

  • IDEMPOTENCY_CONFLICT
    409

    Idempotency-Key a été réutilisé avec un montant, une devise ou un article différent.

  • INVOICE_CONFLICT
    409

    invoiceId a été réutilisé avec un montant, une devise ou un article différent.

  • INVALID_IMAGE_URL
    400

    imageUrl doit être une URL https.

  • ITEM_NAME_REQUIRED
    400

    itemName (ou titre) est requis lorsque les items sont omis.

  • INVALID_ITEMS
    400

    items doit être un tableau non vide d'articles verrouillés (max 20). Chaque ligne a besoin d'un nom et d'un montant.

  • TOO_MANY_ITEMS
    400

    items ne peut pas contenir plus de 20 lignes.

  • AMOUNT_MISMATCH
    400

    le montant doit être égal à la somme de chaque montant de ligne multiplié par la quantité.

  • INVALID_DELIVERY
    400

    Les champs de livraison sont invalides. Chaque champ a besoin d'un nom (max 80) et d'une valeur (max 2048). Le type doit être texte, mot de passe ou zone de texte (texte par défaut). Max 16 champs par ligne.

  • ITEMS_TOO_LARGE
    400

    Les éléments JSON sérialisés sont plus grands que 48 000 caractères.

  • NOT_FOUND
    404

    Aucune session de paiement hébergé ne correspond à cet uid ou invoiceId pour ce vendeur.

  • RESERVE_FAILED
    400

    Le webhook de réservation a expiré, renvoyé des données invalides ou manqué des champs requis.

  • OPTION_AMBIGUOUS
    409

    Plus d'une option de tarification correspond à ce nom. Passez optionId depuis GET stock.

  • OPTION_NOT_FOUND
    404

    Aucune option de tarification ne correspond à cet id ou nom sur cette annonce.

  • OPTION_REQUIRED
    400

    Cette annonce a plusieurs options de tarification. Passez option ou optionId.

  • UNKNOWN_FIELD
    400

    Un nom de champ ne correspond pas au schéma de livraison de cette annonce.

  • FIELD_MAPPING_AMBIGUOUS
    400

    Impossible de mapper les colonnes ou clés aux champs de livraison. Envoyez des en-têtes, ou utilisez des objets d'articles indexés par nom de champ.

  • STOCK_MODE_MISMATCH
    400

    Cette charge utile ne correspond pas au mode de stock de l'option (quantité vs articles sauvegardés).

  • IMPORT_TOO_LARGE
    400

    Une demande de réapprovisionnement peut importer au maximum 1 000 articles sauvegardés par option.

  • OPTION_ITEM_CAPACITY
    400

    Cette option de tarification a déjà le maximum de 5 000 articles sauvegardés non vendus.

  • DUPLICATE_ITEMS
    409

    onDuplicate=error et au moins un article existe déjà sur cette option.

  • UNLIMITED_STOCK
    400

    Cette option a une quantité illimitée. Utilisez set pour passer d'abord à un compte fini.

  • INSUFFICIENT_STOCK
    400

    Pas assez de stock de quantité à retirer.

  • STOCK_HELD_IN_CHECKOUT
    400

    Impossible de réduire la quantité en dessous des unités actuellement réservées dans le panier.

  • INVALID_RESTOCK
    400

    Le corps de réapprovisionnement manque d'une action requise, ou combine add/items dans une option.

Gérer 429

Ralentissez en utilisant Retry-After secondes. Ne faites pas tourner les clés pour contourner les limites ; la limite est par clé et uniforme pour tous les vendeurs.

Les réponses réussies incluent X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset.

Prêt à automatiser ?

Créez une clé dans les paramètres Développeur et connectez Discord ou Telegram sous Notifications.