Русская версия

TgSuperstars Partner Program

Wholesale Telegram Stars, Telegram Premium and other digital goods over a REST API. The third product type is Telegram NFT gifts, see the section below. Payment is up front: you top up a prepaid balance, the partner price of each order is debited from it, and you sell to your end customer at your own price. About the program — partner program; news and case studies — blog.

All monetary values are sent as strings with the currency in the currency field: "13.50", not 13.5. For Stars and Premium that is US dollars (USDT), currency: "USD", two decimals. For NFT gifts it is Toncoin, currency: "TON", three decimals. A string rather than a number is deliberate: amounts are computed with decimal arithmetic, and converting them to float would distort the cents.
How it worksPriceBalance and top-up API v1WebhooksNFT giftsError codesLimits FAQ

How it works

  1. Top up your prepaid balance: in USDT for Stars and Premium, in TON for NFT gifts (these are two separate balances).
  2. Create an order: POST /orders with a mandatory idempotency_key. At that moment the partner price is debited from your balance.
  3. We deliver the Stars or Premium or the NFT gift to the recipient you specify.
  4. If delivery fails, the partner price is returned to your balance automatically.

You take payment from the end customer yourself, in your own system. Your margin is the difference between your price and the partner price.

Price

The price is the same for every partner. There are no tiers, no volume thresholds and no personal discounts, and nothing to negotiate: one number for everyone who connects.

Stars and Premium prices are denominated in USDT; no local-currency exchange rate enters the calculation. The value is recalculated in step with the market, so it is worth re-reading it from the catalog before you order.

NFT gifts are priced in TON. Each gift has its own price, valid while it is on sale; it already includes the transfer to the recipient and the network fee. No TON/USD exchange rate enters the calculation: you pay TON from your NFT balance, and the TON price does not move with the rate. The dollar equivalent (price_usd_ref) is for reporting only, not for payment.

Current prices — GET /products. The exact order amount is fixed in the response to POST /orders; the price_per_star field in the catalog is for reference (6 decimals), you do not need to multiply it yourself.

Balance and top-up

Balances are prepaid purchasing balances: they can only be spent on orders and cannot be withdrawn. The balance for Stars and Premium is kept in US dollars (USDT). The balance for NFT gifts is separate and kept in Toncoin (TON): NFT gifts are bought with TON only, so this balance is topped up with TON only. Funds are not moved or converted between the two: USDT is never spent on NFT gifts, TON is never spent on Stars or Premium.

How to top up. In the dashboard, choose the balance (USDT or TON), the network and an amount — the service will give you an exact amount down to the cent and an address. Send exactly that: the payment is identified by its amount; for TON you may additionally put the code from the request into the transfer comment — optional, it only speeds up review if the amount does not match. USDT is accepted on TON, TRC-20 and BEP-20; TON — Toncoin only, on the TON network, to a separate address shown in the dashboard; the minimum top-up is 10 TON. Crediting is automatic, usually within a minute of network confirmation.

Do not mix up the addresses. The USDT address credits USDT only, the TON address credits Toncoin only. Any other tokens or coins sent to these addresses are not credited and cannot be returned.

Activation. The first top-up starts at $10.00 in USDT or 10 TON. Until it lands, no API key can be issued and API requests are rejected with 403 partner_inactive; the catalog and prices in the dashboard are visible right after sign-up.

Your remaining balances — GET /me: the balance field (USD) and nft_balance (TON), both also in the balances object. If the required balance is not enough, the order is rejected straight away with 402 insufficient_balance (the response carries the currency and balance of that account): no money is reserved and the order does not hang waiting.

API v1

Base: https://pay.tgsuperstars.com/api/partner/v1. Authentication: Authorization: Bearer pk_live_... (the key is issued in the dashboard; optionally an IP whitelist per key). Machine-readable spec: openapi.json.

Idempotency: the same idempotency_key in POST /orders → the same order and exactly one debit. Retrying a request after a timeout is safe — this is the main way to avoid losing or duplicating an order.

Balance and account parameters

curl -H "Authorization: Bearer $KEY" https://pay.tgsuperstars.com/api/partner/v1/me
{
  "partner_id": 100200300,
  "status": "active",
  "balance": "42.50",
  "currency": "USD",
  "balances": {"USD": "42.50", "TON": "12.370"},
  "nft_balance": "12.370",
  "nft_currency": "TON",
  "pricing": {"note": "current prices — GET /products"},
  "limits": {"api_rate_limit": {"per_minute": 120, "burst": 30},
             "stars_min_per_order": 50,
             "stars_max_per_order": 50000,
             "nft_max_order_ton": null, "nft_catalog_rate_limit": {"per_minute": 30},
             "nft_orders_rate_limit": {"per_minute": 60}}
}

balance is the USD balance for Stars and Premium; nft_balance is the TON balance for NFT gifts. The balance/currency fields are kept for compatibility and always refer to the dollar balance.

Catalog with your prices

curl -H "Authorization: Bearer $KEY" https://pay.tgsuperstars.com/api/partner/v1/products
{
  "currency": "USD",
  "stars":   {"price_per_1000": "…", "price_per_star": "…",
              "currency": "USD", "max_per_order": 50000},
  "premium": {"3":  {"partner_price": "…", "currency": "USD"},
              "6":  {"partner_price": "…", "currency": "USD"},
              "12": {"partner_price": "…", "currency": "USD"}},
  "nft": {"available": true, "pricing_model": "per_gift", "currency": "TON", "balance": "nft_balance",
          "catalog": "/nft/gifts", "quote": "/nft/gifts/{gift}", "max_order_ton": null}
}

The example shows no prices: they move with the market, and the current ones come in this response. Amounts are strings with two decimal places; price_per_star has six.

NFT gifts are paid from the TON NFT balance (currency: "TON"); catalog prices and quotes are in TON.

If a price is temporarily unavailable, you get {"unavailable": true, "reason": "cost_unavailable"} instead of prices — this is not an HTTP error but a straight signal that "we are not selling right now". An order will not be accepted at that moment either.

Recipient check (before ordering)

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"type":"stars","username":"@your_customer"}' https://pay.tgsuperstars.com/api/partner/v1/validate/recipient

For premium, the response additionally contains already_premium: true when the recipient currently has an active subscription. This is not a rejection: an order for such an account is accepted, but delivery will be deferred — see Deferred Premium delivery.

Stars order

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"type":"stars","idempotency_key":"ord-1","params":{"quantity":100,"recipient":"@your_customer"}}' https://pay.tgsuperstars.com/api/partner/v1/orders

Premium order

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"type":"premium","idempotency_key":"ord-2","params":{"months":3,"recipient":"@your_customer"}}' https://pay.tgsuperstars.com/api/partner/v1/orders

Deferred Premium delivery

If the recipient already has an active Telegram Premium subscription, the order is still accepted. A subscription cannot be granted on top of an active one, so delivery is deferred and happens on its own as soon as the recipient's current subscription ends. Nothing has to be enabled for this — it applies to every key.

Funds are debited from your balance immediately, when the order is created. The order stays in processing and automatically retries delivery once a day. The waiting time is not known in advance: it equals whatever is left of the recipient's current subscription and may be months.

If deferral does not suit you, call validate/recipient before ordering and skip the order when the response contains already_premium: true.

Valid Premium terms are 3, 6 and 12 months. The third order type is nft — Telegram collectible gifts, see the section below. There are no other types: type accepts stars, premium and nft.

Order response

{
  "order_id": "PA-12345",
  "type": "stars",
  "status": "processing",
  "partner_price": "…",
  "currency": "USD",
  "retail": null,
  "quantity": 100,
  "recipient": "@your_customer",
  "created_at": "2026-08-10T18:20:31.000000",
  "refunded": false
}

Statuses and webhooks

GET /orders/{id} and GET /orders?status=&type=&from=&to=&cursor=&limit=. Statuses: processing | completed | partial | canceled | failed. Outgoing webhooks (order.completed | order.failed | order.refunded) are signed with the header X-Signature: hex(hmac_sha256(body, your webhook_secret)) — verify the signature and handle them idempotently (delivery is "at least once"). Method, headers, body examples and signature verification are in the Webhooks section.

The delivery address (webhook_url in the order body, or the one set in the cabinet) is validated twice. On input — instantly and without DNS: scheme, host, port, no credentials, literal address; a bad address returns 400 bad_webhook_url with a machine-readable reason and the order is not created. Before EVERY delivery attempt the address is validated again, this time with resolution: if the host resolves to a loopback, private or internal address, no request is made at all (the delivery record gets status=failed and rejected_reason), and the connection goes exactly to the verified address — DNS is not re-read between the check and the request. A domain move or a DNS failure does not burn the webhook: it is retried on the 1m/5m/30m/2h/6h schedule. The label field in responses is always in Russian.

Webhooks

We send the order outcome to your address ourselves, so polling GET /orders/{id} is optional. The address and secret are set in the cabinet: the "API" tab, "Webhook" block — the URL field and the "Save" button. The first save creates a secret; it is shown once, so store it on your side. To issue a new one, turn on "Regenerate secret" and click "Save": the old secret stops working immediately, including for retries of webhooks already sent. For a single order the address can be overridden with webhook_url in POST /orders, but the secret still comes from the cabinet: if the "Webhook" block has never been saved, click "Save" once, even with an empty URL. Until both an address and a secret are set, no webhooks are sent, and events from that period are not delivered later — check their outcome via GET /orders/{id}.

Request

POST to your address, Content-Type: application/json, the body is UTF-8 JSON. We wait up to 10 seconds for a response. Redirects are not followed: a 3xx response counts as a failure.

HeaderContents
X-Signaturehex(hmac_sha256(body, webhook_secret)) — body signature
X-TimestampSend time in unix seconds, different for every attempt
X-Signature-V2hex(hmac_sha256(X-Timestamp + "." + body, webhook_secret)) — signature that includes the time, so a captured request can't be replayed later
X-Signature-Version2
X-Webhook-Eventorder.completed, order.failed or order.refunded — same as event in the body
X-Webhook-IdDelivery ID: a 24-character hex string, the same across all retries of one webhook
X-Webhook-AttemptAttempt number: 1, 2, …

The signature is computed over the raw body bytes exactly as received. Verify it before parsing the JSON and never rebuild the string: the body has spaces after : and ,, and non-ASCII characters (…, ⭐, the Russian text in label) are sent as UTF-8 without escaping. Re-serialized JSON gives a different signature.

Body

Always {"event": …, "order_id": …, "data": {…}}. For order.completed and order.failed, data is the order object in the same format as the GET /orders/{id} response, minus the retail field. It is a snapshot taken at the moment of the event. Stars, order completed:

{
  "event": "order.completed",
  "order_id": "pa_6f1c2a9e-1b7d-4c3e-9a51-2f0d8e4b7c10",
  "data": {
    "order_id": "pa_6f1c2a9e-1b7d-4c3e-9a51-2f0d8e4b7c10",
    "type": "stars",
    "status": "completed",
    "partner_price": "…",
    "currency": "USD",
    "quantity": 250,
    "recipient": "@your_customer",
    "created_at": "2026-10-04T12:59:07.329000",
    "completed_at": "2026-10-04T12:59:15.616000+00:00",
    "label": "…",
    "refunded": false
  }
}

Premium is the same object with "type": "premium"; quantity is the number of months.

Order not fulfilled: the money is returned to your balance automatically and two events arrive — order.refunded and order.failed. Their order of arrival is not guaranteed. order.failed carries the order object with status failed or canceled and refunded: true once the money is back on your balance. The refund is also confirmed by a separate order.refunded event, whose body is different:

{
  "event": "order.refunded",
  "order_id": "pa_6f1c2a9e-1b7d-4c3e-9a51-2f0d8e4b7c10",
  "data": {
    "order_id": "pa_6f1c2a9e-1b7d-4c3e-9a51-2f0d8e4b7c10",
    "refund_amount": 1.23,
    "reason": "stars_failed",
    "currency": "USD"
  }
}

refund_amount is a number, not a string: an exception to the money-as-strings rule, kept for compatibility. For NFT refunds (currency: "TON") the body also carries refund_amount_ton, the same amount as a string with three decimals; reconcile money against it. reason is a machine-readable cause such as stars_failed or premium_canceled.

If the transfer was already sent but delivery was not confirmed, there is no automatic refund: the order goes to manual review and no webhooks are sent until it ends. Meanwhile GET /orders/{id} shows status: "failed" and refunded: false. This is not the final outcome, so do not place a replacement order. If the goods arrived, order.completed follows and the status changes to completed; otherwise a refund follows.

Response and retries

Any 2xx response (200–299) received within 10 seconds counts as delivered. We don't read the response body; it can be empty. Anything else — 3xx, 4xx, 5xx, a timeout, a connection error — is a failure: we retry after 1 min, 5 min, 30 min, 2 h and 6 h, up to 6 attempts in total, then stop. Respond right after verifying the signature and do any long processing after the response.

Delivery is "at least once": the same webhook may arrive more than once. Drop duplicates by X-Webhook-Id or by the order_id + event pair. If a webhook never arrives, the order status is always available via GET /orders/{id}.

Verifying the signature

Python:

import hmac, hashlib, time

def verify(raw_body: bytes, headers, secret: str) -> bool:
    key = secret.encode()
    sig = hmac.new(key, raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, headers.get("X-Signature", "")):
        return False
    # X-Signature-V2 includes the time and rejects old requests
    ts = headers.get("X-Timestamp", "0")
    sig2 = hmac.new(key, ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return (hmac.compare_digest(sig2, headers.get("X-Signature-V2", ""))
            and abs(time.time() - int(ts)) < 300)

PHP:

$raw  = file_get_contents('php://input');
$ts   = $_SERVER['HTTP_X_TIMESTAMP'] ?? '0';
$sig  = hash_hmac('sha256', $raw, $secret);
$sig2 = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($sig, $_SERVER['HTTP_X_SIGNATURE'] ?? '')
    || !hash_equals($sig2, $_SERVER['HTTP_X_SIGNATURE_V2'] ?? '')
    || abs(time() - (int)$ts) > 300) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);
http_response_code(200);

Node.js (Express, the body must stay raw: express.raw({ type: 'application/json' })):

const crypto = require('crypto');

function verify(raw, headers, secret) {
  const hex = (data) => crypto.createHmac('sha256', secret).update(data).digest('hex');
  const eq = (a, b) => a.length === b.length && crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
  const ts = headers['x-timestamp'] || '0';
  return eq(hex(raw), headers['x-signature'] || '')
    && eq(hex(Buffer.concat([Buffer.from(ts + '.'), raw])), headers['x-signature-v2'] || '')
    && Math.abs(Date.now() / 1000 - Number(ts)) < 300;
}

Test vector

Use this example to check your implementation. Secret test_secret_123, the body is one line, exactly these bytes:

{"event": "order.completed", "order_id": "pa_6f1c2a9e-1b7d-4c3e-9a51-2f0d8e4b7c10", "data": {"order_id": "pa_6f1c2a9e-1b7d-4c3e-9a51-2f0d8e4b7c10", "type": "stars", "status": "completed", "partner_price": "…", "currency": "USD", "quantity": 250, "recipient": "@your_customer", "created_at": "2026-10-04T12:59:07.329000", "completed_at": "2026-10-04T12:59:15.616000+00:00", "label": "…", "refunded": false}}

Expected signatures: X-Signature = 894ddfb9144d18d41b2965146364acc36b03295b8318d82951f672ce319bc20e; with X-Timestamp: 1791118800 X-Signature-V2 = dab0cc24bb63dd061ad60abeb7313d909be63541748287f3e2154d48e14c9c3f. Turn off the X-Timestamp age check for this example: its time is in the past.

NFT gifts

An NFT gift is a Telegram collectible: a single item with its own number, model, backdrop and symbol, living on the TON network and transferable between accounts. We buy it on the collectible gift market and transfer it to the recipient by username; the wallet and purchase queue are on our side. The gift is delivered anonymously: the sender name is hidden, and neither your brand nor our service is shown to the recipient. You pay from your TON NFT balance — a separate balance topped up with Toncoin only (see Balance and top-up). The key is the same one you use for Stars and Premium.

Every gift is a single item. Each has its own price, valid while the gift is listed. Between the catalog and your order the gift may go to another buyer — then the order is normally rejected before any debit with gift_not_for_sale; if the sale happened after the debit, the order becomes failed and the amount is refunded automatically.

Catalog

curl -H "Authorization: Bearer $KEY" https://pay.tgsuperstars.com/api/partner/v1/nft/collections
curl -H "Authorization: Bearer $KEY" "https://pay.tgsuperstars.com/api/partner/v1/nft/gifts?collection=plushpepe&sort=price_asc"
{
  "currency": "TON",
  "items": [
    {"gift": "plushpepe-1234", "name": "Plush Pepe", "num": 1234, "collection": "plushpepe",
     "image": "https://…/plushpepe-1234.webp", "price_ton": "…", "partner_price": "5.366", "currency": "TON",
     "price_usd_ref": "7.19"}
  ],
  "count": 60, "next_cursor": "60", "source_status": "live"
}

Gift price

curl -H "Authorization: Bearer $KEY" https://pay.tgsuperstars.com/api/partner/v1/nft/gifts/plushpepe-1234
{
  "gift": "plushpepe-1234", "name": "Plush Pepe #1234", "num": 1234, "collection": "plushpepe",
  "image": "https://…", "attributes": {"model": "Frog Prince", "backdrop": "Emerald", "symbol": "Crown"},
  "available": true, "price_ton": "…", "partner_price": "5.366", "currency": "TON", "price_usd_ref": "7.19",
  "quoted_at": "2026-09-17T12:00:00+03:00", "max_order_price": null
}

The gift price: partner_price is the final amount in TON, the same for every partner; the transfer to the recipient and the network fee are already included, nothing is added on top. Rounded up to 0.001 TON. No TON/USD exchange rate enters the calculation: you pay TON from your NFT balance, and the TON price does not move with the rate. The dollar equivalent (price_usd_ref) is for reporting only, not for payment.

available: false means the gift is sold or delisted; an order for it is not accepted.

Recipient check

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"type":"nft","username":"@your_customer"}' https://pay.tgsuperstars.com/api/partner/v1/validate/recipient

The recipient must exist and be reachable. Every example on this page uses the placeholder @your_customer — substitute a real username. A username that does not resolve (a typo, a deleted or hidden account) returns 400 recipient_not_found, and this check runs BEFORE price (only max_price syntax is parsed earlier) — so it masks any other error in the same request. Verify the recipient with the call below before you debug the rest.

Same resolver as for Stars: the recipient is a regular Telegram account, no wallet needed. This is a preliminary check: whether the gift can be transferred to this exact account is confirmed while the order is processed. In the rare case the account exists but cannot receive the gift, the order ends as failed with stage: "create", reason: "invalid_recipient" and a full automatic refund — no money is lost, but double-check the username.

NFT order

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"type":"nft","idempotency_key":"ord-3","params":{"gift":"plushpepe-1234","recipient":"@your_customer","max_price":"5.400"}}' https://pay.tgsuperstars.com/api/partner/v1/orders
{
  "order_id": "PA-12345",
  "type": "nft",
  "status": "processing",
  "partner_price": "5.366",
  "currency": "TON",
  "price_usd_ref": "7.19",
  "retail": null,
  "quantity": 1,
  "recipient": "@your_customer",
  "label": "NFT Plush Pepe #1234 for @your_customer",
  "created_at": "2026-09-17T12:00:05.120000",
  "completed_at": null,
  "refunded": false,
  "gift": {"gift": "plushpepe-1234", "name": "Plush Pepe #1234", "num": 1234, "collection": "plushpepe",
           "image": "https://…", "price_ton": "…",
           "attributes": {"model": "Frog Prince", "backdrop": "Emerald", "symbol": "Crown"}},
  "delivery": null,
  "failure": null
}

Statuses. processing — buying and transferring (usually about a minute; if the market or the network stalls, the order waits in the queue — in rare cases up to an hour); completed — the gift is with the recipient and the transfer is confirmed on-chain; the order object gets a delivery block:

"delivery": {"nft_public_url": "https://t.me/nft/plushpepe-1234",
             "ton_explorer_url": "https://tonviewer.com/transaction/…",
             "transfer_hash": "…", "completed_at": "2026-09-17T12:04:40+03:00"}

failed — the gift was not bought, the amount is refunded to your TON NFT balance automatically (order.refunded event, reason: "nft_failed", refund_amount in TON, currency: "TON") and the order object carries a failure block with stage and reason. stage ∈ create | confirm | queue | pricing | balance; reason is one of gift_not_for_sale (the listing is gone), gift_reserved, price_above_cap (the listing got more expensive than the fixed price — we never overpay), amount_above_cap, invalid_price, invalid_upstream_price, pricing_failed (the listing price could not be confirmed), gift_unavailable (the market did not respond), invalid_recipient, insufficient_funds (temporarily insufficient purchase funds on the service side — your balance is not involved), nft_disabled, hub_rejected, queue_rejected, delivery_failed (anything else). They all mean the same: no purchase happened, the TON is back on your NFT balance. If the purchase went through but the transfer to the recipient did not, the order stays in processing and goes to manual review: we either deliver the gift or refund; there is no automatic refund in that case, so that a gift already bought is not refunded by mistake. The only exception to "failed means no purchase" is the outcome of such a review with a refund: the order becomes failed with stage ∈ create | purchase | transfer and reason: "manual_refund", and the money comes back with an order.refunded event carrying reason: "nft_manual_refund". Contact support at @tg_super_support_bot if an order has been pending for more than an hour.

Webhooks are the same order.completed, order.failed, order.refunded; data carries the order object with the gift, delivery and failure blocks.

Error codes

The format depends on where the error came from. Order processing errors arrive flat:

{"code": "insufficient_balance", "detail": "Insufficient balance"}

Request-level errors — authentication, rate limit, unknown type, a missing idempotency_key — are the same object, but nested in the detail field:

{"detail": {"code": "bad_type", "detail": "type ∈ stars|premium|nft"}}

The difference is historical and kept on purpose, so as not to break existing clients. A reliable parse: take detail, and if it is an object, read code from it, otherwise from the root.

HTTPcodeWhat it means
401unauthorizedMissing or invalid Bearer key
403ip_not_allowedIP not in the key's whitelist
403partner_inactiveDashboard not activated — a first top-up starting at $10.00 is required
429rate_limitedRate limit exceeded — reduce your request rate
400idempotency_requiredNo idempotency_key passed in POST /orders
403partner_program_unavailableThe program is temporarily unavailable
400bad_typetype is not stars, premium or nft
400bad_quantity / bad_monthsInvalid number of stars or subscription term
400bad_giftGift identifier not recognised — take gift from the catalog
400collection_required/nft/gifts called without collection
400bad_cursorInvalid cursor in /nft/gifts — pass next_cursor from the previous response
400bad_max_pricemax_price could not be parsed — a positive TON amount as a string, e.g. «5.400» (the number of decimals is not limited)
400bad_sortUnknown sort in /nft/gifts — only price_asc or price_desc
400bad_attr / bad_paramUnknown filter or query parameter name (compare applied_filters and ignored_params in the response)
409gift_not_for_saleGift already sold or delisted — nothing was debited
409gift_reservedThere is already an unfinished order for this gift
409price_changedPrice is above the max_price you passed — see current_price (TON)
400recipient_required / recipient_not_foundRecipient not specified or not found. The recipient must exist and be reachable in Telegram; it runs before the price checks (only max_price syntax is parsed earlier), so verify the username with /validate/recipient first
400bad_webhook_urlThe webhook_url in the order body is not a valid delivery target — the order was NOT created and the address was not stored. The body carries reason: bad_url (not a string, empty, whitespace or control character, non-ASCII — submit IDN hostnames in punycode), bad_scheme (http/https only; rejecting http is a setting), bad_host (no host, non-ASCII host, percent-encoding in the host), bad_port (port not a number or outside 1..65535; restricting it to 80/443 is a setting), credentials_in_url (credentials in the URL), private_host (the address points to localhost, a private network, an internal zone or our own infrastructure), too_long (a length limit is a setting). The check on input never asks DNS: it is instant and deterministic. If the address is syntactically valid but its DOMAIN resolves into a private network, the order is created and no delivery is ever sent — the delivery record gets status=failed and rejected_reason=private_host, and the next order with that address is rejected with 400 right away
409idempotency_key_reuseSame idempotency_key with different parameters (including the case where a debit is already armed for another order under this key)
409order_in_progressAn order with this key is still being created — retry in a second
409deferred_order_existsThis account already has an unfinished Premium order — wait for it to be delivered
402insufficient_balanceNot enough funds on the balance for this order type: USD for Stars/Premium, TON for NFT — the response carries currency and balance — top up that balance
404not_foundOrder or gift not found (or the order belongs to another partner)
502resolver_errorRecipient resolution failed — try again later
503product_unavailableProduct temporarily unavailable. The body carries reason: product_disabled — switched off on our side, retrying will not help, contact support; upstream_unavailable — the upstream is down, the body and the Retry-After header carry the number of seconds before a retry makes sense
503ledger_unavailableThe idempotency-key reuse check is temporarily unavailable (POST /orders only). The order was NOT created, the key was not claimed and nothing was debited — retry THE SAME request with THE SAME idempotency_key in a few seconds. We will not let the request through unchecked: that check is what stops a reused key for a different lot from being charged the price of the previous order
429rate_limitedRate limit reached. The body carries scope — which one (key for the general limit, validate_recipient, nft_catalog, nft_orders for NFT order creation, per partner) — plus limit, window_seconds, retry_after; the same value is in the Retry-After header

Limits

FAQ

What happens to Stars if delivery fails? We treat it the same way as retail: when the delivery network is down, orders queue up and are delivered once it is back — no money is lost.

I retried after a timeout — will I be charged twice? No, not if you passed the same idempotency_key: you get the same order back and there is still only one debit.

I sent an amount that was not exact to the cent — what now? The payment is not identified automatically and goes to manual review. Contact support at @tg_super_support_bot with the transaction hash.

The gift was sold while I was placing the order? Normally the order is rejected before any debit (gift_not_for_sale). If the sale happened after the debit, the order becomes failed and the TON is refunded to your NFT balance automatically.

Does the recipient need a wallet or Premium? No: the gift is transferred to a regular Telegram account by username.

Who does the gift come from? Anonymously: the sender is hidden. Neither your brand nor our service is shown to the recipient.

Why does the catalog price differ from the card? The catalog is cached for up to a minute and the listing price moves; the amount from the order response is debited, never more than your max_price.

Why a separate TON balance for NFT gifts? NFT gifts are bought and transferred for TON only. If we debited dollars, the rate could move between your order and the purchase and one side would lose the difference. A TON balance removes that risk: the quoted amount is exactly what is debited.

Can I move USDT to the NFT balance or back, or withdraw TON? No. The balances are independent and prepaid: USDT is topped up and spent only on Stars and Premium, TON only on NFT gifts; neither balance can be withdrawn.

An NFT order failed — what about the money? The gift price is returned to your TON NFT balance automatically, just like with Stars. If the purchase went through but the transfer to the recipient did not, the order goes to manual review and we will contact you.

I sent TON to the USDT address (or USDT to the TON address)? Such a transfer is not credited automatically. Contact support at @tg_super_support_bot with the transaction hash.

Support: @tg_super_support_bot — for integration and payment questions. You support your own end customers.