Integration recipes

FazerCards API Cookbook

Concrete problems with full solutions in curl, Node.js, Python and Go. Each recipe has the problem statement, the approach narrative, and copyable code that works against the live FazerCards REST API.

quick-startorderidempotency

Place your first wholesale order

Problem

You have an API key from the FazerCards panel and want to confirm the full path — pick a SKU, place an order, retrieve the code — in under five minutes.

Solution

List the catalog with `GET /catalog`, pick any product (Amazon $10 gift card is the canonical first SKU), then `POST /order` with the SKU id and an `Idempotency-Key`. The response either carries the code directly (for code-based SKUs) or returns an order id you can poll via `GET /order/{id}` until status is `completed`. Webhooks (next recipe) replace polling in production.

curl
# 1. Browse the catalog
curl -s "https://api.fzr.cards/api/v2/catalog" \
  -H "X-Api-Key: $FAZER_API_KEY" | jq '.items[0]'

# 2. Place an order (Amazon $10, US storefront)
curl -s -X POST "https://api.fzr.cards/api/v2/order" \
  -H "X-Api-Key: $FAZER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"sku_id":"amazon-us-10","quantity":1}'

# 3. Retrieve order status + code
curl -s "https://api.fzr.cards/api/v2/order/<ORDER_ID>" \
  -H "X-Api-Key: $FAZER_API_KEY" | jq
webhookssigningevents

Handle order webhooks (order.completed / failed / refunded)

Problem

You don't want to poll `GET /order/{id}` every few seconds. You want FazerCards to call your server when an order is fulfilled, so your Telegram bot or storefront can deliver the code to the customer instantly.

Solution

Register your HTTPS endpoint in the reseller panel under Settings → Webhooks. FazerCards POSTs JSON with the event type (`order.completed`, `order.failed`, `order.refunded`), the order id and a signed `X-FazerCards-Signature` header (HMAC-SHA256 of the raw body, using your webhook secret). Verify the signature before processing — always respond 2xx within a few seconds; long work (DB writes, customer notification) belongs in a background queue.

node
import express from "express";
import crypto from "crypto";

const app = express();
const secret = process.env.FAZER_WEBHOOK_SECRET!;

app.post("/webhooks/fazercards", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.header("X-FazerCards-Signature") ?? "";
  const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send("bad signature");
  }

  const event = JSON.parse(req.body.toString("utf8"));

  switch (event.type) {
    case "order.completed":
      // event.order.id, event.order.code → deliver to customer
      enqueueDelivery(event.order);
      break;
    case "order.failed":
      enqueueRetry(event.order, event.reason);
      break;
    case "order.refunded":
      enqueueCustomerRefund(event.order);
      break;
  }

  res.sendStatus(200);
});

app.listen(8080);
telegramtopupdirect-id

Sell PUBG Mobile UC through a Telegram bot

Problem

You want a Telegram bot that accepts a player's PUBG ID, takes payment, calls FazerCards to top up that account directly, and replies with confirmation — fully automated, sub-minute fulfillment.

Solution

PUBG UC is a direct-ID top-up — there is no code returned, FazerCards credits the player by id. The bot collects the PUBG ID + UC amount in chat, charges the customer (Telegram Stars, USDT, your local rail — your choice), POSTs the order with `player_id` in the metadata, and either waits for the synchronous response or for the `order.completed` webhook before replying to the user.

node
import { Bot } from "grammy";
import { randomUUID } from "crypto";

const bot = new Bot(process.env.BOT_TOKEN!);
const FAZER = "https://api.fzr.cards/api/v2";
const headers = {
  "X-Api-Key": process.env.FAZER_API_KEY!,
  "Content-Type": "application/json",
};

// /buy_uc 60 5123456789
bot.command("buy_uc", async (ctx) => {
  const [ucAmount, playerId] = ctx.match.split(" ");
  if (!ucAmount || !playerId) return ctx.reply("Usage: /buy_uc <amount> <player_id>");

  // (omitted: collect payment from customer first)

  const order = await fetch(`${FAZER}/order`, {
    method: "POST",
    headers: { ...headers, "Idempotency-Key": randomUUID() },
    body: JSON.stringify({
      sku_id: `pubg-uc-${ucAmount}`,
      quantity: 1,
      metadata: { player_id: playerId },
    }),
  }).then(r => r.json());

  if (order.status === "completed") {
    await ctx.reply(`✓ ${ucAmount} UC delivered to ${playerId}`);
  } else {
    await ctx.reply(`Order ${order.id} in progress — you'll get a confirmation in a minute.`);
    // The webhook handler (previous recipe) sends the confirmation
  }
});

bot.start();
idempotencyreliability

Use Idempotency-Key to make order POSTs safe to retry

Problem

Your network can flake on `POST /order`. You don't want a retry to create a second order and double-charge the player.

Solution

Generate a fresh UUID per logical order on your side, send it in the `Idempotency-Key` header. If the same key arrives twice (because the first response was lost), FazerCards returns the result of the original operation — no duplicate. Re-using the same key with a different body is an error. Keep the key around for at least the time you'd retry on a failure (a few minutes is enough; FazerCards stores key→result for over 24h).

node
import { randomUUID } from "crypto";

async function placeOrderWithRetry(sku: string, qty: number) {
  const idempotencyKey = randomUUID();
  const body = JSON.stringify({ sku_id: sku, quantity: qty });

  for (let attempt = 0; attempt < 5; attempt++) {
    try {
      const res = await fetch("https://api.fzr.cards/api/v2/order", {
        method: "POST",
        headers: {
          "X-Api-Key": process.env.FAZER_API_KEY!,
          "Idempotency-Key": idempotencyKey,  // same key across retries
          "Content-Type": "application/json",
        },
        body,
      });
      if (res.status >= 500) throw new Error(`5xx ${res.status}`);
      return await res.json();
    } catch (err) {
      const wait = 2 ** attempt * 250;
      await new Promise(r => setTimeout(r, wait));
    }
  }
  throw new Error("all retries exhausted");
}
catalogpagination

Discover available SKUs by category and region

Problem

Your storefront sells gift cards. You want to render a price list grouped by brand (Amazon, Steam, PSN, …) and by region (US, UK, TR, …).

Solution

`GET /catalog` returns the full catalog with categories, brands, regions, denominations and wholesale prices. The response is paginated; use the `cursor` query param to walk the pages. Cache the catalog response on your side (5-15 min TTL is typical) — wholesale prices move slowly enough.

curl
# First page
curl -s "https://api.fzr.cards/api/v2/catalog?category=gift-cards&limit=100" \
  -H "X-Api-Key: $FAZER_API_KEY" | jq

# Next page (use cursor from previous response)
curl -s "https://api.fzr.cards/api/v2/catalog?category=gift-cards&limit=100&cursor=eyJpZCI6IjAwYWE..." \
  -H "X-Api-Key: $FAZER_API_KEY"
balancemonitoring

Monitor reseller balance and alert before it runs out

Problem

If your FazerCards balance hits zero mid-day, orders start failing and your customers see errors. You want a background job that checks balance every minute and pings you on Telegram below a threshold.

Solution

Poll `GET /balance` on a one-minute schedule, compare against your threshold (e.g. $100 of expected daily volume), and send a Telegram message via the Bot API if you drop under it. Don't re-send the same alert every minute — track last-alert state in your job's storage.

node
import cron from "node-cron";

const THRESHOLD = 100; // USD
let lastAlertAt = 0;

cron.schedule("* * * * *", async () => {
  const res = await fetch("https://api.fzr.cards/api/v2/balance", {
    headers: { "X-Api-Key": process.env.FAZER_API_KEY! },
  });
  const { balance_usd } = await res.json();

  if (balance_usd < THRESHOLD && Date.now() - lastAlertAt > 30 * 60 * 1000) {
    await fetch(`https://api.telegram.org/bot${process.env.BOT_TOKEN}/sendMessage`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        chat_id: process.env.ADMIN_CHAT_ID,
        text: `⚠️ FazerCards balance low: $${balance_usd.toFixed(2)}`,
      }),
    });
    lastAlertAt = Date.now();
  }
});
rate-limitreliability

Handle HTTP 429 rate-limit responses correctly

Problem

On bursty traffic your client occasionally gets `HTTP 429 Too Many Requests`. FazerCards uses per-category limits — typically the bottleneck is the catalog category (30/min) if you do not cache locally, or order-create (60/min) on a promotion peak. You want to slow down per the response, not just hammer in a tight loop.

Solution

FazerCards returns a `Retry-After` header (seconds) on 429. Sleep for at least that long, then retry — preferably with a small jitter (±15 %) so multiple workers don't desync into the next limit window together. For sustained load, throttle outbound calls on your side per category (cache catalog for 5–15 min, batch balance/account reads) instead of relying on retry loops.

node
async function callWith429Handling(input: string, init?: RequestInit) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(input, init);
    if (res.status !== 429) return res;

    const retryAfterSec = Number(res.headers.get("Retry-After") ?? 1);
    const jitter = Math.random() * 0.3 + 0.85; // 0.85x .. 1.15x
    await new Promise(r => setTimeout(r, retryAfterSec * 1000 * jitter));
  }
  throw new Error("rate-limited beyond retry budget");
}
migrationadapter

Migrate an existing integration from Reloadly / Bitrefill

Problem

You already integrated against another reseller API (Reloadly, Bitrefill, Codashop) and want to switch the underlying supplier to FazerCards with minimal application-code changes.

Solution

Build a thin adapter on your side that exposes the API shape your app already uses, and translates to FazerCards underneath. Most reseller flows boil down to three calls — catalog, order, status — plus a webhook receiver. The adapter is usually 100–200 lines and lets you swap suppliers without touching app code.

node
// Adapter that mimics a generic "ResellerSupplier" interface used by your app.
// Swap this for a Reloadly / Bitrefill implementation without touching callers.

import { randomUUID } from "crypto";

export class FazerCardsSupplier {
  private api = "https://api.fzr.cards/api/v2";
  constructor(private apiKey: string) {}

  private headers(extra: Record<string, string> = {}) {
    return { "X-Api-Key": this.apiKey, "Content-Type": "application/json", ...extra };
  }

  async listProducts(category: string) {
    const r = await fetch(`${this.api}/catalog?category=${category}`, { headers: this.headers() });
    return (await r.json()).items;
  }

  async placeOrder(sku: string, qty: number, metadata: Record<string, string> = {}) {
    const r = await fetch(`${this.api}/order`, {
      method: "POST",
      headers: this.headers({ "Idempotency-Key": randomUUID() }),
      body: JSON.stringify({ sku_id: sku, quantity: qty, metadata }),
    });
    return r.json();
  }

  async getOrder(id: string) {
    const r = await fetch(`${this.api}/order/${id}`, { headers: this.headers() });
    return r.json();
  }
}

Full API reference is on /docs

The cookbook covers typical workflows. The full list of endpoints, schemas and error shapes is in the main API docs + OpenAPI spec.

Open API docs