Headless Storefront — ForgeStore Docs
Features Digital Products Pricing Calculator Games FAQ Docs Partners Login Get Started Free
Advanced

Headless Storefront

Build your own fully custom store UI while using ForgeStore as the payment backend. Perfect for large gaming communities who want a unique store experience.

What is Headless?

With a headless integration, your website/game server communicates directly with the ForgeStore API. You design your own store UI — your own HTML, CSS, React, whatever you want. ForgeStore handles payments, commands, subscriptions, and player management behind the scenes.

Architecture

Your Website/Game
  ↓ GET /api/v1/packages (list products)
  ↓ POST /api/v1/checkout (create session)
  ↓ Redirect player to checkout_url
ForgeStore handles payment
  ↓ Webhook POST to your server
Your plugin executes commands

Quick Start

1. Get your API key

Portal → API → Copy your key. Pass it as a header on every request:

HTTP Header
X-ForgeStore-Key: fs_live_xxxxxxxxxxxxxxxxxxxx

2. Fetch your packages

JavaScriptJS
const API_KEY = 'fs_live_xxxx';
const BASE    = 'https://forgestore.net/api/v1';

async function getPackages() {
  const res = await fetch(`${BASE}/packages`, {
    headers: { 'X-ForgeStore-Key': API_KEY }
  });
  const { data } = await res.json();
  return data; // array of packages
}

// Example response:
// [
//   { id: 1, name: "VIP", price: 999, currency: "EUR", type: "single" },
//   { id: 2, name: "VIP+", price: 1999, currency: "EUR", type: "subscription" }
// ]

3. Create a checkout

JavaScriptJS
async function buyPackage(playerId, packageIds) {
  const res = await fetch(`${BASE}/checkout`, {
    method: 'POST',
    headers: {
      'X-ForgeStore-Key': API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      player_id:   playerId,
      package_ids: packageIds,  // [1] or [1, 2, 3] for basket
      email:       '[email protected]', // optional
    }),
  });
  const { checkout_url } = await res.json();
  window.location.href = checkout_url; // redirect player to payment
}

4. Receive webhooks

After payment, ForgeStore calls your webhook URL with the order details:

Node.js webhook handlerJS
app.post('/webhook/forgestore', express.json(), (req, res) => {
  const { event, order_id, player, packages, amount, currency } = req.body;

  if (event === 'order.completed') {
    console.log(`${player} bought ${packages.map(p => p.name).join(', ')}`);
    // Give player their items...
    giveRank(player, packages);
  }

  res.sendStatus(200);
});

JavaScript SDK

Drop-in SDK for browser-based stores. No build step required.

Install via CDNHTML
<script src="https://forgestore.net/sdk/v1/forgestore.js"></script>
UsageJS
const fs = new ForgeStore({ apiKey: 'fs_live_xxxx' });

// Load and render packages into a container
fs.renderStore('#my-store-container', {
  theme: 'dark',        // 'dark' | 'light' | 'custom'
  currency: 'EUR',
  playerIdField: '#player-input', // input element with player name
  onCheckout: (url) => window.location.href = url,
});

// Or fetch manually
const packages = await fs.getPackages();
const { checkout_url } = await fs.checkout({
  player_id: 'Steve',
  package_ids: [1, 2],
});
window.location.href = checkout_url;

Package response format

Each package returned by GET /api/v1/packages includes:

ResponseJSON
{
  "id": 42,
  "name": "Elite Rank",
  "description": "Get 5000 points instantly!",
  "description_long": "Full details shown in a popup…",
  "price": 1900,
  "currency": "USD",
  "type": "single",
  "image_url": "https://…",
  "category": "Ranks",
  "variables": { "pointToGive": "5000" },
  "is_active": true
}

Custom variables: keys defined in the package's Metadata / Variables section (portal → package → edit) are exposed in variables. Any {key} placeholder written in description or description_long — for example {pointToGive} — is already substituted server-side before the API responds, so you can render the text as-is. Use the raw variables object when you need the values themselves (badges, calculators, custom UI).

Complete HTML example

index.htmlHTML
<!DOCTYPE html>
<html>
<head>
  <title>My Server Store</title>
  <script src="https://forgestore.net/sdk/v1/forgestore.js"></script>
</head>
<body>
  <input id="player" placeholder="Your Minecraft username">
  <div id="store"></div>

  <script>
    const fs = new ForgeStore({ apiKey: 'YOUR_KEY' });

    async function loadStore() {
      const packages = await fs.getPackages();
      const container = document.getElementById('store');

      packages.forEach(pkg => {
        const div = document.createElement('div');
        div.innerHTML = `
          <h3>${pkg.name}</h3>
          <p>${pkg.description || ''}</p>
          <strong>€${(pkg.price / 100).toFixed(2)}</strong>
          ${pkg.description_long ? `<button onclick="showDetails(${pkg.id})">Details</button>` : ''}
          <button onclick="buy(${pkg.id})">Buy</button>
        `;
        container.appendChild(div);
      });
    }

    // Popup de détails quand une description longue existe —
    // même comportement que le storefront ForgeStore natif.
    async function showDetails(packageId) {
      const packages = await fs.getPackages();
      const pkg = packages.find(p => p.id === packageId);
      if (!pkg || !pkg.description_long) return;
      // Remplacez par votre propre modal — description_long peut contenir
      // plusieurs paragraphes, affichez-la dans un élément scrollable.
      alert(pkg.name + "\n\n" + pkg.description_long);
    }

    async function buy(packageId) {
      const player = document.getElementById('player').value;
      if (!player) return alert('Enter your username');
      const { checkout_url } = await fs.checkout({
        player_id: player,
        package_ids: [packageId],
      });
      window.location.href = checkout_url;
    }

    loadStore();
  </script>
</body>
</html>

POST /api/v1/checkout — Body Parameters

Parameter Type Required Description
player_id string Yes Player identifier — username, Discord ID, or any opaque string. Passed through unmodified for your own delivery matching.
package_ids array Yes One or more package IDs, e.g. [157] or [157, 158] for a basket.
email string No Pre-fills and locks the checkout email field.
lang string No 'en' or 'fr'. Sets checkout language. Defaults to the buyer's browser language if omitted.
hide_giftcard int No Set to 1 to hide the gift card field on checkout.
embed_origin string No Bare https origin (e.g. https://example.com) to allow embedding checkout in an iframe/modal on your site. Signed server-side.
return_url string No Must start with https://. Where the "Back to Store" button points after a successful order, instead of the default ForgeStore-hosted store page.

Embedding checkout in an iframe

By default, the checkout page refuses to load in an iframe (standard clickjacking protection). To embed it on your own site, pass embed_origin in your checkout request — ForgeStore signs the returned URL server-side, you never compute any signature yourself.

const res = await fetch('https://forgestore.net/api/v1/checkout', {
  method: 'POST',
  headers: { 'X-ForgeStore-Key': API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    player_id: 'Steve123',
    package_ids: [157],
    embed_origin: 'https://your-site.com'   // bare https origin, no path
  })
});
const { checkout_url } = await res.json();
// Use checkout_url AS-IS as the iframe src — it already includes the signature.
document.getElementById('fs-frame').src = checkout_url;
<iframe id="fs-frame"
  allow="payment"
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation"
  style="width:100%;border:0;min-height:600px">
</iframe>
⚠ PayPal and card payments open in a popup or full-screen redirect — they cannot be displayed inside an iframe (this is enforced by PayPal/Stripe, not something ForgeStore controls). Your iframe must allow popups (see sandbox attributes above), or payment will silently fail to open. Crypto checkout stays fully inside the iframe.

Rate Limits

Plan Rate limit Webhooks
Free 60 req/min 3 endpoints
Pro 600 req/min Unlimited
Enterprise Unlimited Unlimited + Discord format

Error Codes

Error responseJSON
{
  "error": "Invalid or inactive API key.",
  "code": 401
}
CodeMeaning
200 Success
400 Bad request — missing fields
401 Invalid API key
404 Resource not found
422 Validation error
429 Rate limit exceeded
500 Server error