Advertising rules (opens in a new tab)
/ DEVELOPERS
Dashboard

PIXORADS / DEVELOPER DOCUMENTATION

Pixora Ads SDK and API for developers

Publisher monetization and website campaign tracking: practical guides, working code and troubleshooting in one place.

PIXORADS / DATA FLOW01 — 06
  1. 01Your property
  2. 02Your backend
  3. 03PixorAds API
  4. 04Native offer
  5. 05Verified action
  6. 06Publisher revenue

Your key stays on your server. PixorAds verifies the completed action before publisher income is recorded.

01 / GETTING STARTED

How monetization works

#

Select formats when registering your property for moderation. Images and video are creative types; native, interstitial and rewarded are delivery scenarios. There is no separate showVideo() method: the server selects a matching creative.

MethodWhere the ad appearsViewer reward
showNative()Image/video card in your containerNone
showInterstitial()Overlay inside your Mini App; immediately dismissibleNone
showRewarded()Overlay after explicit user opt-inAfter verified viewing; internal bonuses require your backend

Mini App advertising stays inside the partner interface. Advertiser navigation is optional; dismissing the ad returns to your screen. Bots use conversation ads paid per click; channels use posts with CPM or CPC. The CTA leads to the advertiser. This JavaScript SDK is for Mini Apps, not messages.

Publisher income and viewer bonuses are separate. Use native or interstitial to monetize without rewards. Ordinary impressions require at least 50% creative visibility for one continuous second in an active window, and actual playback for video. The server checks eligibility, limits and budget before confirming delivery.

For new integrations use SDK 1.8.1: init() and an explicit show method. Legacy create()/mount() remain compatible.

02 / GETTING STARTED

Quick start

#
  1. Open Monetization → Ad network and choose Mini App, bot or public channel.
  2. Provide a name, Telegram URL and a description of the topic and audience. For Mini Apps use the bot link or a t.me/bot/appname direct link. Select native, interstitial and/or rewarded before moderation. Describe the ad placement in the description; there is no separate field.
  3. Verify control: bot token for bots/Mini Apps; administrator permissions for channels. The token verifies control of the bot; a moderator reviews the placement.
  4. For Mini Apps and bots, create an API key after approval and keep it server-side. Automatic channel posting needs no separate server or API key: save the posting settings and verify bot permissions.
  5. Run the test below, connect your backend and SDK or prepare an ad in the dashboard. Live ads require an active property and matching demand; connecting does not guarantee ad fill or income.
03 / GETTING STARTED

See a working example

#

The @pixorAds_networkbot demo shows the complete user journey: opening a Mini App, requesting an ad through a server and showing the chosen format inside the Mini App. You can try it before connecting your own property.

  1. Open the bot and launch its Mini App. The demo backend verifies the Telegram session through /api/v1/ad-network/demo/session.
  2. The SDK calls /api/v1/ad-network/demo/offer on the same origin. The secret publisher key stays on the server.
  3. If there is no matching paid offer, the demo app shows an introductory video. This is the publisher's own empty state, not an ad view: it creates no charge, reward or publisher income.
Demo: mount SDK after Telegram verification
// Demo only: run after loading pixorads-publisher.js inside Telegram.const session = await fetch("/api/v1/ad-network/demo/session", {  method: "POST",  credentials: "same-origin",  headers: { "Content-Type": "application/json" },  body: JSON.stringify({ init_data: window.Telegram.WebApp.initData })});if (!session.ok) throw new Error("Telegram session could not be verified");const { csrf_token, publisher_id } = await session.json();const placement = window.PixorAdsPublisher.init({  element: document.getElementById("pixorads"),  endpoint: "/api/v1/ad-network/demo/offer",  csrfToken: csrf_token,  publisherId: publisher_id,  language: "en"});await placement.showNative();
04 / INTEGRATION

Mini App · JavaScript / React

#

Load the script once in HTML. Set endpoint to your own backend on the same origin. Never expose the API key to the browser. Before init(), send Telegram.WebApp.initData to your backend: verify its signature and age, create a session, and for cookie authentication return a CSRF token for the csrfToken option. Copy the public publisherId from the property card. Examples below are native ads without rewards; see Show methods for other scenarios. Pass the session CSRF token as a React prop and destroy() on unmount.

HTML
<!-- Render this page AFTER your backend verifies the Telegram session. --><!-- Your backend inserts its session CSRF token into this meta tag. --><meta name="csrf-token" content="YOUR_SESSION_CSRF_TOKEN"><script src="https://pixora-ads.com/sdk/pixorads-publisher.js"></script><div id="pixorads"></div><script type="module">  const placement = window.PixorAdsPublisher.init({    element: document.getElementById("pixorads"),    endpoint: "/api/pixorads/offer",    csrfToken: document.querySelector('meta[name="csrf-token"]').content,    publisherId: "YOUR_PUBLIC_MINIAPP_PUBLISHER_ID",    trackAudience: false,    personalized: false,    language: "en"  });  const state = await placement.showNative();  if (state.status === "error") console.warn(state.code);  // empty: no ad available. Continue the app; do not retry in a loop.  // On screen removal: placement.destroy();</script>
05 / INTEGRATION

Show methods

#
MethodWhere the ad appearsViewer reward
showNative()Image/video card in your containerNone
showInterstitial()Overlay inside your Mini App; immediately dismissibleNone
showRewarded()Overlay after explicit user opt-inAfter verified viewing; internal bonuses require your backend

Choose one example below. Load the SDK and have your backend verify the Telegram session first. Replace publisherId with your public property ID. csrfTokenFromYourSession is your session CSRF token, not an API key. /api/pixorads/offer is a route you implement on your backend.

native
// Prerequisites: SDK loaded, your session verified, <div id="ad-slot"></div>.const ads = window.PixorAdsPublisher.init({  publisherId: "YOUR_PUBLIC_MINIAPP_PUBLISHER_ID",  endpoint: "/api/pixorads/offer",  csrfToken: csrfTokenFromYourSession,  element: document.getElementById("ad-slot"),  language: "en",  trackAudience: false, // Enable explicitly if you measure app activity.  personalized: false});const state = await ads.showNative();if (state.status === "empty") {  // No matching ad. Continue your app normally; try later.} else if (state.status === "error") {  console.warn("Ad unavailable:", state.code);}// ready means rendered, NOT a paid impression.// Keep ads alive while the card is visible.// When removing this screen: ads.destroy();

Method results

CallResult
init(options)Creates a controller without showing an ad. Invalid options throw synchronously. Enables trackAudience by default.
showNative()Promise<PublisherState>: ready, empty or error with code. ready means rendered, not billed.
showInterstitial() / showRewarded()Promise resolves on dismissal. Errors and no_fill reject — use try/catch. Early dismissal returns completed:false.
getState()Latest SDK event; not financial status or balance.
trackVisit()Promise<{recorded:boolean}>. Missing publisherId/signed Telegram data, test mode or network failure returns recorded:false.
destroy()Cancels requests, removes the card/overlay and listeners. Create a new controller for a new screen.
PublisherShowResult
{  shown: true,              // Viewer handshake completed; NOT proof of payment  completed: false,         // true only for completed rewarded viewing  impression_confirmed: true // Ordinary viewer confirmation, when available  // receipt, session_id: present when supplied by the viewer  // test:true => {shown:false, completed:false, test:true}; no overlay or payout}

Only one ad viewer can be open at a time. Do not run showNative() and a full-screen method concurrently on one controller. Continue your app on no_fill; never replace a promised reward with an ordinary ad. Destroy the controller when its screen is removed, not immediately after rendering a card.

06 / INTEGRATION

Rewards and verification

#

Native and interstitial need no reward integration. In rewarded mode PixorAds verifies the view; your backend grants any additional coins, lives or other bonus inside your app.

  1. Read the opaque receipt from showRewarded() after dismissal. It is proof to verify, not a reward amount.
  2. Send it to your authenticated backend. Verify it using this property's key and match telegram_id against your own session user.
  3. In one transaction save the unique receipt_id and grant the server-configured bonus. Repeated requests must return the existing outcome without granting twice.
reward/verify · your backend only
// Inside YOUR authenticated, CSRF-protected reward route.// req.user.telegramId must come from a verified session, never the request body.const response = await fetch("https://pixora-ads.com/api/v1/ad-network/publisher-api/reward/verify", {  method: "POST",  headers: {"Content-Type": "application/json", "X-PixorAds-Key": process.env.PIXORADS_PUBLISHER_KEY},  body: JSON.stringify({receipt: req.body.receipt}),  signal: AbortSignal.timeout(8000)});if (!response.ok) throw new Error("Reward not verified");const proof = await response.json();if (!proof.rewarded || String(proof.telegram_id) !== String(req.user.telegramId)) {  throw new Error("Wrong viewer");}// YOUR database transaction (required; not implemented by this snippet):// 1. Insert (receipt_id, your_user_id) into a ledger with UNIQUE(receipt_id).// 2. On a new row, grant YOUR server-configured internal bonus.// 3. On duplicate, return the existing result for the same user.// Commit both operations together, then return 2xx to the browser.// Never take a reward amount from req.body or credit proof.reward as app coins.

Response: rewarded, receipt_id, session_id, publisher_id, telegram_id, operation_id, reward, currency, completed_at. Receipts last seven days and are property-bound. Verification transfers no money. If your backend is unavailable, retry the same receipt instead of requiring another ad view.

When to use onReward

onReward({receipt, session_id}) fires on completion while the viewer can remain open. It is an alternative to processing the receipt after dismissal. The SDK neither awaits nor retries your async callback: implement errors and retries yourself. If you use both paths, server deduplication by receipt_id is essential. onEvent notifications and CTA clicks never authorize a bonus.

API amounts are integer minor units: XTR / 1000 = Stars; RUB / 1000 = rubles; USD / 1,000,000 = dollars. reward:70 with currency:XTR means 0.07 Stars. In an offer, viewer_reward/viewer_currency describe the viewer reward; reward and publisher_amount use the campaign currency. publisher_income estimates USD earnings, fixed on confirmation. Neither defines the number of coins in your app.

07 / INTEGRATION

Backend & API key

#

Your backend authenticates the user, rate-limits requests and requests an ad from PixorAds. Forward the JSON response to the widget. Reuse one Idempotency-Key for retries of the same logical request; use a new key for a new offer.

Server request
curl https://pixora-ads.com/api/v1/ad-network/publisher-api/offer \  -H "X-PixorAds-Key: $PIXORADS_PUBLISHER_KEY" \  -H "Idempotency-Key: unique-logical-request-id" \  -H "Content-Type: application/json" \  -d '{"language":"en","format":"native"}'
Node.js / Express
// Node.js 22+ / Express. Keep this code on YOUR SERVER.// app, requireYourSession, verifyYourCsrf and yourRateLimit// belong to your app. Implement them before exposing the route.import { randomUUID } from "node:crypto";app.post("/api/pixorads/offer", requireYourSession,  verifyYourCsrf, yourRateLimit, async (req, res) => {    res.set("Cache-Control", "no-store");    const language = req.body?.language ?? "en";    const context_token = req.body?.context_token ?? "";    // Legacy create().mount() omits format; preserve the API's auto default.    const format = req.body?.format ?? "auto";    if (!["ru", "en"].includes(language)) return res.status(400).json({error:{code:"invalid_language"}});    if (typeof context_token !== "string" || context_token.length > 2048) return res.status(400).json({error:{code:"invalid_viewer_context"}});    if (!["auto", "native", "interstitial", "rewarded"].includes(format)) return res.status(400).json({error:{code:"invalid_format"}});    // Reuse this ID if you retry this upstream request.    const requestId = randomUUID();    try {      const response = await fetch(        "https://pixora-ads.com/api/v1/ad-network/publisher-api/offer",        {          method: "POST",          headers: {            "Content-Type": "application/json",            "X-PixorAds-Key": process.env.PIXORADS_PUBLISHER_KEY,            "Idempotency-Key": requestId          },          body: JSON.stringify({ language, context_token, format }),          signal: AbortSignal.timeout(8000)        }      );      const retryAfter = response.headers.get("Retry-After");      if (retryAfter) res.set("Retry-After", retryAfter);      const payload = await response.json();      // Preserve structured error.code so the SDK can show useful diagnostics.      res.status(response.status).json(payload);    } catch {      res.status(503).json({ error: { code: "upstream_unavailable" } });    }  });

Rotating or revoking a key disables it and its placement links. Update your environment variable and create new placements. Started sessions retain their terms, but new viewer actions check current status and key version.

08 / INTEGRATION

Bots · conversation ads

#

The main bot format is an ad after a useful reply, paid per confirmed visit. BotFather tokens stay on the owner server; viewers need no separate registration.

Guide, SDK and free test delivery ↗

Configure delivery, approve creatives and view rates and earnings in the bot property card under Conversation ads. Rewarded viewing remains an optional format with its own integration.

09 / INTEGRATION

Channels · CPM and CPC

#

Add a public channel with your account and the PixorAds bot as administrators. After moderation, check posting and deletion permissions. Choose views (CPM, default) or confirmed clicks (CPC) in channel settings. Set minimum earnings and the result cap per post. Automatic posting and approval are available for both.

CPM pays Telegram counter views in the first 24 hours, including forwards; this is not unique reach or completion. CPC pays navigation confirmed on a short page without registration. Views, previews and repeats to the same advertiser in channels within 24 hours are free. An IP and User-Agent fingerprint is not proof of a unique person. Readers receive no reward.

Settlement follows the 24-hour placement, then the campaign hold. Post history shows earnings, paid views or clicks and release dates. Channel prices and shares are independent of Mini Apps; current rates appear in property earnings.

10 / INTEGRATION

Offerwall · tasks & rewards

#

Demo showcase · SDK 1.3.2

Try 10 task types without registering a property. The separate DemoClient returns a sample catalog and simulates verification in browser memory. It never calls the financial API, issues receipts or confirms real rewards. This demonstrates the UI, not your backend integration.

Open demo showcase →
JavaScript · DemoClient
// Load /sdk/pixorads-offerwall.js first.const demo = new PixorAdsOfferwall.DemoClient();const { items } = await demo.catalog();const attempt = await demo.start(items[0].id, "demo-1");await demo.check(attempt.id); // demo_completed; paid=false; no receiptdemo.reset();

Offerwall 1.3 · Connection, a nonfinancial sandbox, bonuses and diagnostics in one dashboard.

Task opening and reports · SDK 1.3

After start, call client.open(attemptId) and open its url. This arms reaction/poll observation or creates a protected website link. Do not use the raw advertiser URL. Bots can open hosted_url to continue the same attempt in the hosted interface.

Reports: client.report(id), submitReport(id, {version, body, images, draft}, key); product answers use {version, answers}. Upload up to 3 screenshots with uploadReportImage(id, base64). Appeal with appeal(id, {version, reason}, key). Reuse the same key for retries. report_updated reports status and deadline, never reward eligibility. Grant only after backend verification of reward_eligible.

One task catalog: a hosted screen, Mini App component and API for your own interface or Telegram bot. Public channel subscriptions and group joins are supported. PixorAds must be an administrator of the target chat. First bot launches and advertiser-confirmed Mini App actions are also supported after an integration test. Public-group reactions, non-anonymous PixorAds polls, new Premium boosts, website visits, surfing and report tasks including product research are supported. Website availability depends on service settings. Dedicated purchase formats and multi-step tasks are not supported.

1. Prepare your property

Add and verify a property under My properties and complete moderation. In Tasks & rewards choose the reward mode, configure callbacks and enable tasks. Only campaigns whose advertisers explicitly enabled partner distribution are eligible.

Choose task types

Select Task types in Offerwall settings: for example subscriptions, Mini App actions or report tasks. All 10 supported types are selected by default. Clearing every type stops new tasks from being offered. Actual availability also depends on campaigns, service settings and the user.

The server applies your selection to every integration: hosted screen, SDK, component and bot API. The next catalog refresh reflects the settings without integration changes. Started tasks remain in My progress: users can complete them, check them and receive the agreed reward even if that type is no longer accepted.

client.catalog() returns the server's options.task_types selection, available items and history of started tasks. Earlier responses may omit the field. Never filter history by options.task_types or pass a custom list to Client, connect or start: only the property owner changes these settings in the dashboard.

  • PixorAds reward: a registered user earns cash; the property earns a share of the platform fee. Initial share: 50% of the fee.
  • Property reward: you grant coins or access; your property earns a share of the result price. Initial share: 80%. Your server delivers internal rewards.

Shares are configurable and frozen when a task starts. Property income is recorded in USD at a frozen exchange rate and held after final verification (24 hours by default). Membership retention is shown separately. A confirmed action does not mean income is immediately withdrawable.

Task referral rewards are funded from the platform share remaining after the publisher split. They do not reduce the agreed user reward or property income.

2. Choose an integration

Before enabling tasks, open Monetization → Offerwall → Connection. Save settings, create a sample task and complete it. Its event has test=true, user_id=null and zero monetary income. Never credit a real user for it. Repeat the test after changing settings.

With webhooks, the test is sent to the saved URL with the usual HMAC signature. Otherwise fetch GET /publisher-api/tests. Your server acknowledges it through POST /publisher-api/tests/{conversion_id}/ack with X-PixorAds-Key. HTTP 2xx alone does not confirm a reward. Test events remain available for 24 hours.

Hosted screen: copy the Telegram link from offerwall settings. It works in Mini Apps, bots and channel posts. Source attribution is preserved and does not create a referral relationship.

HTML
<script src="https://pixora-ads.com/sdk/pixorads-offerwall.js"></script><pixorads-task publisher-id="YOUR_PUBLISHER_UUID" theme="light"></pixorads-task>

The component runs inside a Telegram Mini App using signed initData for the bot verified on the property. Never expose an API key in the browser. Theme: light/dark; height: CSS variable --pixorads-offerwall-height.

JavaScript
const client = new PixorAdsOfferwall.Client({ publisherId: "YOUR_PUBLISHER_UUID", telegramInitData: Telegram.WebApp.initData });// Show terms and obtain consent before connect().const { items, history, options } = await client.connect({ accepted: true });// items already honor the publisher's task_types on the server.// Show an empty state when items is empty; always render history separately.// Call only when the user chooses a task from items.async function startSelectedTask(campaignId, retryKey) {  const task = await client.start(campaignId, retryKey);  const destination = await client.open(task.id);  return { task, url: destination.url };}// Open the returned protected URL; offer a separate Check button.async function checkTask(attemptId) {  return client.check(attemptId);}// Do not grant a reward based on a JavaScript event.

API base: https://pixora-ads.com/api/v1/offerwall. Append the /viewer/... and /publisher-api/... paths shown here. Client({baseUrl}) accepts only an HTTPS origin, such as https://pixora-ads.com, without a path, query or hash.

On screen removal call client.disconnect(): pending requests fail with disconnected and the viewer token is cleared. Obtain consent and reconnect before further use. After a lost response, reload history; retry start or report submission with the original key because the server may have saved the request. The SDK does not retry automatically.

3. Bot API

Ready examples include /bonus and /bonusbalance, start/check buttons, reconciliation after restart, setup tests and reward deduplication. They poll the API and do not need a public webhook.

Each file starts with setup commands and environment variables. Examples credit a standalone SQLite balance. For your game currency, put the credit and UNIQUE(conversion_id) in one transaction in your own database; acknowledge only after commit. Do not start a second polling process for an existing bot: integrate the handlers into its app.

Your bot server uses the property API key in X-PixorAds-Key. Request a session for a Telegram ID. If verified=false, show verification_url so the user can verify their Telegram account and accept terms. An arbitrary ID cannot earn rewards. Identity proof lasts one day; server requests do not extend it.

Python · httpx
import os, httpxBASE = "https://pixora-ads.com/api/v1/offerwall"KEY = os.environ["PIXORADS_KEY"]async def get_tasks(telegram_user_id):    async with httpx.AsyncClient(timeout=20) as api:        r = await api.post(BASE + "/publisher-api/session",            headers={"X-PixorAds-Key": KEY},            json={"telegram_id": telegram_user_id})        r.raise_for_status()        session = r.json()        if not session["verified"]:            return session  # Show verification_url as a button.        token = session["viewer_token"]        r = await api.get(BASE + "/viewer/catalog",            headers={"Authorization": "Bearer " + token})        r.raise_for_status()        return {"verified": True, "token": token, **r.json()}# Call only after the user selects a task.async def start_task(token, campaign_id, retry_key):    async with httpx.AsyncClient(timeout=20) as api:        r = await api.post(BASE + "/viewer/start",            headers={"Authorization": "Bearer " + token,                     "Idempotency-Key": retry_key},            json={"campaign_id": campaign_id})        r.raise_for_status()        return r.json()# Send title, reward and conditions. POST /viewer/attempts/{id}/open# with the viewer token and use its url; for reports use hosted_url.# A separate Check callback POSTs /viewer/attempts/{task_id}/check.# Keep tokens on your server and bind callbacks to the same user.# Store retry_key before the request and reuse it on retries.# If items is empty, continue the ordinary bot flow.

A user has one separate quick-task slot and can take up to five report tasks across maps, marketplaces and extended tasks. Review and waiting for delivery do not count towards this limit. Existing tasks returning for revision or from delivery remain available. New assignments are unavailable while the limit is reached. Browsing does not reserve budget, and restarting the same campaign through another property does not create a second reward.

Handle HTTP 409 task_assignment_limit when starting a task: the report-task limit is reached. task_quick_assignment_limit means the quick-task slot is occupied. Show a clear message and keep the selected card; do not switch to a different execution automatically. These errors create no new assignment or reserve. Once capacity is available, retry. After a network error, reuse the same command key.

4. Deliver rewards on the server

Configure a public HTTPS webhook. The signing secret is in offerwall settings. X-PixorAds-Signature = HMAC-SHA256(secret, timestamp + '.' + raw body bytes); timestamp is X-PixorAds-Timestamp. Use constant-time verification and a bounded clock skew (for example 5 minutes). Do not reserialize JSON before verification.

  • completed — action observed, membership retention pending. An early bonus is allowed only for the explicitly enabled advance mode;
  • qualified — final verification passed: grant the internal reward once;
  • revoked — retention verification failed; no payout;
  • paid — property income released from hold.

Before crediting, request GET /publisher-api/rewards/{conversion_id}. Check telegram_id, reward_mode=partner, test=false and reward_eligible=true. Credit reward_units from that response exactly once. After your transaction commits, POST /publisher-api/rewards/{conversion_id}/ack with {reference: 'your credit identifier'}. Retry with the same reference after network errors. This acknowledgement appears to the user and in analytics; it does not trigger PixorAds money transfers.

5. Flexible bonuses

Choose fixed units or units per $1 of property income. Units are rounded down and bounded by your minimum and maximum. Promotion multipliers have an end date. Terms freeze at task start; advertiser prices and the monetary split stay unchanged. Catalog estimates can change before start if settings or exchange rates change.

By default, grant after final verification. In advance mode, the property grants its own bonus after the initial check at its own risk: failure to retain membership cancels the property income. PixorAds does not claw back spent internal bonuses. Cash rewards and income release are not accelerated.

Use conversion_id as the reward key and save it atomically with the reward under a UNIQUE constraint. Deduplicate event_id. Events can be repeated or arrive out of order. Return 2xx only after durable storage. Up to 12 delivery attempts; errors and manual retries are available in the property dashboard.

If using the receipt from a final result, send it to your server: POST /publisher-api/reward/verify with the property key. Match telegram_id and conversion_id to your user. A receipt does not replace payout deduplication. Without webhooks, poll history and final results.

Advertisers: bot launches and Mini App actions

Create a bot or Mini App campaign. Open the Offerwall 1.2 wizard in its settings: action → integration → test → launch. Set the exact condition, server event name, deadline and verification period. Bot launches require a new user; Mini App goals may allow existing users. Each result is credited once.

Saving a server goal excludes the campaign from native tasks. After the test select Partners only. Campaign funding, moderation and its agreed price still apply. Set the goal and deadlines in a draft before moderation. Create a new campaign to change approved conditions.

Links contain start=px_… for bots or startapp=px_… for Mini Apps. Bind the token to a Telegram ID from an authentic bot update or validated initData. Never trust a browser-supplied user ID or completion flag. Opening a link or validating initData does not itself prove goal completion.

POST /api/v1/offerwall/actions/postback
{  "campaign_id": "CAMPAIGN_UUID",  "attempt_token": "px_ATTEMPT_TOKEN",  "event_id": "your-stable-event-id",  "telegram_id": 123456789,  "event_name": "tutorial_complete",  "occurred_at": "2026-10-06T10:05:00Z",  "first_seen_at": "2026-10-06T10:01:00Z",  "status": "completed",  "reason": null}

Sign X-PixorAds-Signature = hex(HMAC-SHA256(secret, timestamp + '.' + rawBody)); X-PixorAds-Timestamp is Unix seconds, with a 5-minute tolerance. Store the campaign wizard secret on your server only. On retries keep the body and event_id, updating timestamp and signature. Retries do not create extra payments; a changed payload with the same event_id returns 409.

first_seen_at is the original first appearance of the account in your service, not the ad click time. Preserve history across account deletion. New-user tasks require it to be at or after task start. occurred_at is the completion time. Both dates require a timezone. Confirmation must arrive before the deadline. A signature authenticates the sender; the advertiser remains responsible for truthful history and goal evidence.

Send status=rejected to reject a result, or revoked to revoke a prior confirmation. Include reason: existing_user, conditions_not_met, fraud or user_cancelled. Revocation is allowed before final settlement, including the verification period. After settlement, result_final requires a support investigation. A rejected attempt cannot be re-confirmed.

For HTTP 200, inspect accepted and reason; processing is not a payment promise. A background worker processes confirmed results. Wizard diagnostics show execution and publisher income states separately. Invalid signatures return 401, invalid payloads 422, rate limits 429. Retry persisted events from your outbox on network failures or 5xx.

Create test issues a separate one-hour token bound to your Telegram ID. It runs the same validation without budget reservations, credits or reward notifications. Use synthetic dates after test creation for a positive test, but only real database history for live results. Settings or key changes require a new test. Replaced keys immediately stop working, including for existing attempts.

Download Python server helper: signing, initData validation, event submission

Reporting and limits

Pass placement to Client, as a component attribute, or in an API session request. Use 1–40 ASCII letters, digits, _ or -. The widget reports a card when at least 50% is visible. In a custom UI call client.visible([campaignId]) after actual display, not catalog loading. Bot message visibility is unknown, so do not report card visibility there.

Support is tied to an execution: POST /viewer/attempts/{id}/ticket with {message: 'description'}, or client.support(id, message). The response appears in history[].ticket. Publishers see their own requests; operators respond under Ad network → Offerwall support.

The dashboard reports starts, retention checks, confirmed results, cancellations, advertiser spend and property income. Periods refer to task start date. A catalog request is not an ad impression, unique reach or billable action. Billing includes only final verified results.

11 / INTEGRATION

Audience and targeting

#

Use init({publisherId, trackAudience:true}); this is init()'s default. Set false to opt out. trackVisit() uses signed Telegram initData at most one hour old. Installation only in an ad slot measures its visitors, not your entire app audience.

Call trackVisit() on meaningful returns without polling. The server deduplicates people. recorded:false means no observation was saved, not an ad failure. trackAudience:false disables automatic submission but does not block an explicit trackVisit() call.

Bots report genuine activity via POST /api/v1/ad-network/publisher-api/activity with {telegram_id} and a server-only X-PixorAds-Key. These are partner-reported observations, not paid-view evidence. Channels refresh subscriber counts after admin verification; subscribers are not advertising reach.

Activity over 24 hours / 7 / 30 days, unique ad viewers, visible impressions and completed views are separate. Forecasts need seven full UTC days for every selected property; unsupported targeting history produces no estimate. Measured audiences are deduplicated; channel subscribers are not summed. Groups below 20 are hidden from advertisers.

Disclose measurement to users. Enable personalized:true only with permission to use an existing PixorAds profile. Never substitute initDataUnsafe for signed initData. Activity observations use HMAC identifiers retained for up to 90 days.

12 / REFERENCE

Integration test

#

The real SDK below runs with test: true. No ads are requested and no money or statistics change. Click the card button. Use the same mode on your page; it requires no endpoint.

Test mode
window.PixorAdsPublisher.mount({  element: document.getElementById("pixorads"),  language: "en",  test: true});
13 / REFERENCE

SDK options and events

#
OptionPurpose and default
publisherIdPublic Mini App UUID. Required for embedded viewing and measurement; not a secret key.
endpointYour same-origin POST offer route, required with test:false. Receives language, format and available context_token.
elementRequired for showNative() and create(). Optional for full-screen methods through init().
csrfTokenYour cookie-session CSRF token, sent to your endpoint as X-CSRF-Token.
languageru = Russian, en = English (default). Controls SDK UI and campaign language matching: the chosen language or All languages. Does not translate creative text or video. Pass the current app language.
timeoutMs1000–30000 ms, default 10000. Offer-fetch timeout; viewer startup has a separate 20-second timeout.
testfalse. True disables ad/measurement requests and payments. Native renders a test card; full-screen methods do not open a viewer.
trackAudienceinit(): true; create()/mount(): false. Automatically calls trackVisit() at creation. Set explicitly for your measurement policy.
personalizedfalse. Use an existing PixorAds profile only with user permission; the SDK does not show a consent prompt.
embeddedAlways enabled by init(). Defaults to false in legacy create()/mount(): the rewarded card opens separate PixorAds viewing.
onRewardCallback with {receipt, session_id}; your backend must verify it. Not awaited or retried by the SDK.
onEventloading, ready, empty, error, opened, completed, closed, impression_reported, test_clicked, destroyed. UI events, not financial guarantees.

New integrations: init() → chosen method → destroy() when removing the screen. Legacy create() requires element; mount() uses auto selection and returns a state promise. PixorAdsPublisher.mount(options) exposes no controller; use init() or create() for lifecycle management.

Runtime and CSP

Live embedded viewing requires HTTPS and a Telegram launch with signed initData at most one hour old. Use test:true in an ordinary browser; initDataUnsafe is not identity proof. Refresh expired launch data by reopening the Mini App, not by changing auth_date.

Allow https://pixora-ads.com in script-src, frame-src and connect-src; allow card assets in img-src and media-src. Retain your app’s existing permissions. If CSP restricts styles, check compatibility: the SDK applies inline styles to its card and overlay. Never send the API key to the browser.

TypeScript: save the .d.ts in a folder included by tsconfig and use window.PixorAdsPublisher. These declarations describe a global script, not an npm module. There is no official npm package.

Download TypeScript declarations
14 / REFERENCE

Publisher API reference

#
POST/api/v1/ad-network/publisher-api/offer

Request a native, interstitial or rewarded offer from your server. The publisher key must never be sent to a browser or Mini App.

AUTHENTICATIONX-PixorAds-KeySecret publisher key
HEADERSContent-Type: application/jsonIdempotency-Key: …
JSON BODY{ "language": "ru" | "en", "format": "native" | "interstitial" | "rewarded" }language defaults to en; format defaults to auto (legacy). Forward the SDK context_token unchanged when supplied.
RESPONSES200 · 401 · 409 · 422 · 429See error handling below
Server request
curl https://pixora-ads.com/api/v1/ad-network/publisher-api/offer \  -H "X-PixorAds-Key: $PIXORADS_PUBLISHER_KEY" \  -H "Idempotency-Key: unique-logical-request-id" \  -H "Content-Type: application/json" \  -d '{"language":"en","format":"native"}'

LOCAL PLAYGROUND

Build a safe request

ru = Russian, en = English. This language is used for SDK UI and campaign matching, including campaigns marked All languages. Creatives are not translated. Pass your app language; default: en. This example sends no request.

Response example

Below is an abbreviated rewarded example. Return the full upstream JSON unchanged, including URLs, format and accounting fields. Native/interstitial use billing = confirmed_impression and include impression_url. reward and publisher_amount are integer minor units: divide XTR and RUB by 1000, USD by 1,000,000. Viewer reward: viewer_reward/viewer_currency. Estimated USD publisher earnings: publisher_income.

200 · rewarded · excerpt
{  "source": "pixorads",  "placement_id": "<placement UUID>",  "title": "<advertisement title>",  "text": "<plain text>",  "disclosure": "Реклама · Ad · PixorAds",  "url": "https://t.me/pixoradsbot?startapp=net_<32 hex characters>",  "expires_at": "<ISO 8601 timestamp>",  "format": "rewarded",  "billing": "completed_rewarded_session",  "currency": "XTR",  "reward": 70,  "publisher_amount": 1}// No suitable demand: { "source": "none", "monetag_enabled": false }// Rewarded response excerpt, not a complete offer to return from your backend.// Example: 70 XTR minor units = 0.07 Stars; 1 = 0.001 Stars.// Ordinary: billing:"confirmed_impression", reward:0, impression_url present.// Forward the FULL upstream JSON unchanged; never build it from this excerpt.

Ordinary placements last five minutes; rewarded placements last 24 hours. Follow expires_at. Idempotency-Key: up to 80 characters, one per logical request. Rewarded retries replay the response. Repeating an issued ordinary offer returns placement_already_issued: request a fresh placement with a new key. Avoid retry loops.

15 / REFERENCE

Errors & troubleshooting

#
CodeWhat to check
same_origin_backend_requiredPoint endpoint to your backend, not directly to PixorAds
authentication_required / 401 / 403User session, CSRF and a valid server API key
publisher_unavailable / 409Moderation, pause, suspension and network availability
invalid_publisher_keyCorrect key for the property; check whether it was revoked
placement_expired / 410Create a new placement with a new Idempotency-Key
rate_limited / 429Back off retries; Mini Apps: 60 requests/minute per property; bot CPC: 120; IP limits also apply
timeout / network_errorBackend availability, HTTPS, CSP connect-src and timeout
invalid_adForward API JSON without rewriting the URL; do not return a login HTML page
no_fill / source: noneNo matching demand. Keep an empty state and try later
publisher_format_unavailableThe selected format is not enabled for this property. Check its declared formats.
viewer_in_progressWait for the current viewer to close; avoid parallel calls. Recreate the controller after destroy().
placement_already_issuedAn ordinary placement was already issued. Request a fresh one with a new Idempotency-Key.
verified_viewer_requiredProvide publisherId and valid Telegram.WebApp.initData. Anonymous pixels cannot confirm paid impressions; use the current SDK.
impression_unconfirmedThe impression was not confirmed. Rendering is not earnings; check property status and Telegram-session freshness.
16 / REFERENCE

Statistics & support

#

Property statistics show issued sessions, verified completions, held income and released income. Confirmed ordinary impressions are separate from rewarded completions; an offer request, card render or click alone does not prove earnings. Earnings accrue in USD. Mini Apps: 80% during the current promotion, then 70%; new channel campaigns: 90%, then 88%; bot CPC has an independent rate. Current rates appear in property earnings. Ordinary ads use paid impression revenue; Rewarded uses revenue after the viewer reward. Channel prices and shares are fixed at campaign creation and FX at post reservation. Mini App terms are fixed at issuance and USD at confirmation. Historical RUB and Stars remain separate. USD withdrawals use crypto only. Rates depend on auction bids and available demand. Restrictions can keep income under review.

Contact support with the property ID, error time, HTTP status and code. Do not send your API key or bot token.

17 / FOR ADVERTISERS

Ad network formats

#

Select formats when registering your property for moderation. Images and video are creative types; native, interstitial and rewarded are delivery scenarios. There is no separate showVideo() method: the server selects a matching creative.

MethodWhere the ad appearsViewer reward
showNative()Image/video card in your containerNone
showInterstitial()Overlay inside your Mini App; immediately dismissibleNone
showRewarded()Overlay after explicit user opt-inAfter verified viewing; internal bonuses require your backend

Mini App advertising stays inside the partner interface. Advertiser navigation is optional; dismissing the ad returns to your screen. Bots use conversation ads paid per click; channels use posts with CPM or CPC. The CTA leads to the advertiser. This JavaScript SDK is for Mini Apps, not messages.

Publisher income and viewer bonuses are separate. Use native or interstitial to monetize without rewards. Ordinary impressions require at least 50% creative visibility for one continuous second in an active window, and actual playback for video. The server checks eligibility, limits and budget before confirming delivery.

For new integrations use SDK 1.8.1: init() and an explicit show method. Legacy create()/mount() remain compatible.

18 / FOR ADVERTISERS

Pixor Pixel

#
PIXOR PIXEL / SIGNAL LAYER

From ad click to action on your site

Pixel remembers the anonymous px_click and shows what visitors do after following an ad. Link it while creating a website campaign or in that campaign’s settings.

01Create a PixelDashboard → Pixels and events. Add and verify your HTTPS domain.
02Install the snippetAdd the script to your site. In test mode, confirm PageView and your target event in the journal.
03Link a campaignPromote → New campaign → Website traffic → Audience step. Choose a Pixel in its own panel. For an existing campaign, open its settings.
04Verify and go liveCheck test events, then open the verified HTTPS site through a real website campaign visit: it creates px_click. Confirm the outcome with a signed server postback.
CAMPAIGN API

Link without rotating a key

The dashboard saves your selection immediately. For automation, use the internal UUID id from GET /api/v1/pixels; the public px_… pixel_id is for the script. Send null to disconnect. Only the campaign owner can make this change.

PUT /api/v1/campaigns/{campaign_id}/attribution/pixel
{ "pixel_id": "<pixel UUID>" }
Install on your site
<script>  window.PixorAdsPixel = window.PixorAdsPixel || { q: [] };  window.PixorAdsPixel.track = window.PixorAdsPixel.track || function (name, options) {    window.PixorAdsPixel.q.push([name, options]);  };  window.PixorAdsPixel.consent = window.PixorAdsPixel.consent || function (value) {    window.PixorAdsPixel.q.push(["consent", value]);  };</script><script async data-consent="required" src="https://pixora-ads.com/sdk/pixorads-pixel.js?pixel_id=px_your_id"></script><script>// Call from your real CMP consent callback, never automatically:function onAnalyticsConsentChanged(granted) {  window.PixorAdsPixel.consent(granted ? "granted" : "denied");}  // Only after your server accepts the lead:  // window.PixorAdsPixel.track("Lead", { eventId: acceptedLead.event_id });</script>

Connect your CMP consent callback: this snippet collects no events before consent. Browser events show the funnel; a signed postback with the same event_id confirms an analytics conversion without changing billing. A direct site URL does not create px_click. Keep the signing key on your backend only.

19 / FOR PARTNERS

Referral postbacks

#

Send referral milestones to Keitaro, Binom v2 or your server. This is outgoing analytics: it does not change payouts or replace Offerwall reward webhooks or Pixel server confirmation.

1. Connect your tracker

Partners → Links → Postbacks: choose a tracker, enter its public HTTPS endpoint, add goals and saved links. Preview the generated request, test delivery and enable the integration. One integration can serve several links; each link has one integration.

Open partner links →

2. Preserve your tracker's click ID

Tracker offer URL
# Keitarohttps://pixora-ads.com/api/v1/referrals/track/YOUR_LINK_CODE?tracker_click_id={subid}# Binomhttps://pixora-ads.com/api/v1/referrals/track/YOUR_LINK_CODE?tracker_click_id={clickid}

Use the code from your saved link; your tracker substitutes its click macro. tracker_click_id is the external identifier (up to 128 characters); click_id is Pixora's separate internal ID. The bridge saves the external ID and opens the selected website, bot or Mini App. A direct Telegram link with fixed sub_id values does not capture the tracker's dynamic click ID.

3. Choose goals and amounts

EventCondition
Registration complete
registration
Once after email confirmation or Telegram verification and acceptance of the required terms.
5–15 tasks completed
tasks
Configurable paid-task threshold; separate goals for 5, 10 and 15.
Earnings threshold
earnings
Confirmed task earnings in the selected currency, once per goal.
Balance deposited
deposit
First or every credited deposit at or above the selected amount.
Partner commission released
commission
Confirmed, available referral commission.

Up to 24 goals per integration. amount is the event amount; payout is partner income: a referral's 1,000 RUB deposit is not 1,000 RUB of partner revenue. Only commission generates monetary payout by default. Currencies remain separate; Stars are not converted to rubles or dollars. In the settings API, amount_minor is an integer string: RUB and XTR use 1,000 units, USD 1,000,000. Outgoing amount and payout are decimal strings in their currencies.

Keitaro receives a stable tid per goal and event so retries do not add another conversion. In Binom, reserve event1…event24 and one payout currency, RUB or USD. The adapter sends absolute event values and cumulative revenue with disable_upsell=1 and disable_postback=1. Do not add them again in your tracker rules.

Custom endpoint: GET or POST

GET · URL example
https://tracker.example/pixora?click={tracker_click_id}&goal={goal_id}&event={event}&amount={amount}&currency={currency}&payout={payout}&payout_currency={payout_currency}

Macros are allowed only in query values: tracker_click_id, click_id, event_id, goal_id, event, link_id, sub_id_1…sub_id_5, amount, currency, payout, payout_currency and occurred_at. Wrap macro names in braces. status, goal and amount_minor are not macros. event_id and tracker_click_id are added to custom GET requests even when omitted from your template.

POST sends fixed JSON with those fields plus schema_version=1, test_mode, event_type and corrects_event_id; it is not a custom body template. Store event_id with a UNIQUE constraint in the same transaction as processing, and return 2xx for duplicates. An event_type=correction references its original event through corrects_event_id. Never credit real users from test_mode=true events.

Delivery, retries and journal

Preview sends no request. A delivery test uses a synthetic px_test_… ID; HTTP 2xx proves only an endpoint response, not a tracker conversion. For an end-to-end check, follow your tracker link and reach a goal. The journal shows the event, status and error reason while masking secret parameters.

Network failures, 429 and 5xx are retried with backoff, up to 8 automatic attempts per cycle. Other 4xx and redirects stop delivery. Manual retry keeps the original event_id, URL and payload. A failed event blocks later events for that external click: restore the endpoint and retry it. Setting changes apply to future visits without resending history. The original referral attribution is preserved.

Management API: /api/v1/referrals/postbacks, authenticated owner only. GET lists and POST creates; /{id} supports GET and PUT; /{id}/events is the journal; /preview and /{id}/preview render a request; /{id}/test and /events/{event_id}/retry explicitly deliver. Create, save, test and retry require Idempotency-Key (1–96 characters); updates need the current version. The publisher X-PixorAds-Key is not used here.