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.
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
↓ 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:
X-ForgeStore-Key: fs_live_xxxxxxxxxxxxxxxxxxxx
2. Fetch your packages
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
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:
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.
<script src="https://forgestore.net/sdk/v1/forgestore.js"></script>
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:
{
"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
<!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. |
| 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>
sandbox attributes above), or payment will silently fail to open. Crypto checkout stays fully inside the iframe.
Rate Limits
Error Codes
{
"error": "Invalid or inactive API key.",
"code": 401
}
| Code | Meaning |
|---|---|
| 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 |