GPTBuilder/Ads
Publisher console Apply
Developer portal

Build a reward moment
players trust.

Everything needed to integrate GPTBuilder Ads into a game, website or app: SDK setup, server verification, postbacks, reversals and testing.

1. Getting started

GPTBuilder Ads is a survey-based rewarded ad network. Publishers offer a reward-required action in their product. A player completes a qualifying CPX Research survey, CPX sends a signed server-to-server completion, and the platform verifies, records and reports it before any reward is granted.

The flow

  1. Your server or browser SDK creates a short-lived reward session for a user.
  2. The reward gate displays the reward and one qualifying survey. Survey links stay on the GPTBuilder Ads domain.
  3. CPX sends a completion to /api/cpx/postback with the documented MD5 hash.
  4. Our server records a pending conversion in the immutable ledger and notifies your configured postback endpoint.
  5. Your server calls the verification API with its site secret, then grants the reward exactly once.
Never trust the browser redirect

A survey being opened, a tab closing, or a client-side “complete” message is not proof. Only the server verification endpoint or a verified postback should unlock value.

Requirements

  • An approved publisher account and a site key.
  • A public HTTPS integration URL for browser sites (ownership verification uses /.well-known/gptbuilder-ads.txt).
  • A server endpoint or trusted backend to verify completions before granting rewards.
  • For server API calls: a site API secret of at least 32 characters that you choose and store securely.

2. Applying as a publisher

Create an account with Firebase Authentication — email/password or Google — verify your email, then submit an application from the dashboard. Every application needs:

  • Publisher / studio name and primary contact.
  • Game or website name and URL.
  • Description of the product and platform (web, Android, iOS, Unity, Flutter, other).
  • Estimated traffic and reward mechanics.
  • Exact explanation of when the survey is required and what the reward is.

Applications are reviewed manually. You can be asked to provide changes. Only approved integrations can serve surveys, and maintenance mode pauses demand-side delivery globally.

Reward policy

Do not reward users for opening a survey, for partial completion, or for screen-outs. Do not disguise a survey as content. The reward must be optional and clearly explained before the survey starts.

3. Getting approved

After approval, the integration page gives you a public site key and the option to create a server API secret. Domain ownership is verified by placing this file on your site:

GET https://your-site.example/.well-known/gptbuilder-ads.txt

gptbuilder-ads-verification=gba-your-token-from-the-dashboard

The file must be served over HTTPS and contain the exact line. Click Check verification file in the dashboard. For native games without a web property, provide your store listing or product URL and coordinate manual verification with support.

Create your API secret

Generate a random secret with at least 32 characters, save it in your server environment, then submit it in the dashboard. The platform stores only a one-way scrypt hash and can never show it again. Rotate it from the same screen if it leaks.

# Generate a suitable secret locally
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# Store it server-side, never in client JavaScript
SURVEY_API_SECRET=your-64-character-secret

4. Installing the SDK

The SDK is a single dependency-free script. It creates sessions, opens the first-party reward gate and relays a completion hint. That hint is advisory: call the server verification API before granting anything. The SDK never receives a CPX survey link or a secret.

<script src="https://ads.gptbuilder.space/sdk.js"></script>

<script>
  const rewards = new SurveyRewards({
    siteKey: 'pk_live_your_public_site_key',
    externalUserId: currentPlayer.id,
    rewardLabel: '50 gold coins',
    rewardValue: '50',
    rewardCurrency: 'coins',
    customParameters: { level: currentPlayer.level }
  });

  rewards.openRewardGate({
    placement: 'level_7_unlock',
    onComplete(result) {
      // IMPORTANT: verify on your server before granting value.
      fetch('/api/rewards/claim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sessionId: result.sessionId })
      });
    },
    onCancel(result) {
      console.log('Left without reward:', result.reason);
    },
    onNoSurveys(result) {
      console.warn('No qualifying surveys', result.fallback);
    },
    onError(error) {
      console.error('Reward gate error', error.message);
    }
  });
</script>

SDK options

OptionRequiredDescription
siteKeyYesPublic site key from the dashboard. It identifies the approved integration.
externalUserIdYesStable ID for the player in your product. Use your internal user ID, not an email.
rewardLabelNoHuman-readable reward name shown before the survey.
rewardValue / rewardCurrencyNoDisplay values for the reward. The platform does not convert or grant game currency itself.
customParametersNoFlat object of strings, numbers or booleans. Max 20 keys, 200 chars per value. Available in postbacks as {custom.key}.
sessionTokenSigned modeFive-minute site token minted by your server from POST /api/v1/site-tokens. Required when the site has an API secret.
apiBaseNoDefaults to the origin serving sdk.js.

Methods

  • openRewardGate(options) — creates a session and opens the reward overlay. Returns a Promise with the session.
  • getSessionStatus(sessionId, token) — reads the current verified status for that session.
  • close(reason) — closes the overlay without granting a reward.
  • lastResult() — the last result emitted by the reward page.
Client results are hints

onComplete fires only after the reward page has polled a server-verified completion, but you must still call the server verification API from your backend before crediting a player. An attacker can call your client callbacks directly.

Test surveys & rewarded video ads

Admins can enable two zero-value alternatives while CPX access is being approved or while testing an integration. They are intentionally separate from CPX revenue and publisher balances.

No cash value

Test and video completions are labelled isTest / isVideoAd and have revenueSource set to test or video. They create no ledger entries, publisher earnings, withdrawals or postbacks. Do not treat them as paid CPX conversions.

Test surveys

When enabled in Admin → Settings → Test surveys & video ads, the reward gate shows Open test survey. It opens a separate tab, lets you click Complete test survey, records a zero-value test conversion and closes. The reward gate updates through polling and a same-origin broadcast channel.

The server verification response for the session includes verified: true, isTest: true, revenueSource: "test", and publisherEarningsCents: 0. Your backend should handle test completions separately and must not grant a paid reward for them.

Rewarded video ads

Paste an unlisted YouTube watch, share, embed or Shorts URL in the same admin card. The gate loads the YouTube IFrame player inline, hides controls, disables keyboard seeking, locks playback to 1×, and rejects forward/rewind jumps detected by the client. The server records the start time and video duration, then refuses completion until the elapsed wall-clock time covers the full video at normal speed.

If YouTube blocks the server-side duration lookup, the player-reported duration is used with a minimum floor. Video completions are labelled isVideoAd: true, revenueSource: "video", and have zero earnings.

YouTube playback is not cryptographic proof

YouTube embeds cannot prove that a human watched every second; a modified client can forge player events and progress. The gate blocks ordinary speed, seek and control bypasses, but treat video completion as an engagement signal, not a strong fraud boundary for high-value rewards.

5. Integrating a game

Use the SDK when your game runs in a WebView or HTML environment. For a native game, create sessions from your trusted backend, then open the returned reward URL in a secure browser/WebView and poll/verify server-side.

WebView / JavaScript game

  1. Choose a reward-required action, such as a revive, level unlock, or currency top-up.
  2. Call openRewardGate from a direct user gesture.
  3. Do not apply the reward in onComplete until your server verifies it.
  4. On your backend, call POST /api/v1/conversions/verify with the session ID.
  5. Store a claim record keyed by the returned transaction ID to guarantee one reward.
// Backend example: one reward per verified transaction
app.post('/api/rewards/claim', async (req, res) => {
  const { sessionId } = req.body;
  const auth = Buffer.from(
    `${process.env.SURVEY_SITE_KEY}:${process.env.SURVEY_API_SECRET}`
  ).toString('base64');

  const verified = await fetch(
    'https://ads.gptbuilder.space/api/v1/conversions/verify',
    {
      method: 'POST',
      headers: {
        Authorization: `Basic ${auth}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ sessionId })
    }
  ).then(r => r.json());

  if (!verified.ok || !verified.data.verified) {
    return res.status(409).json({ error: 'Survey not verified' });
  }

  const transactionId = verified.data.transactionId;
  const alreadyClaimed = await db.claims.findOne({ transactionId });
  if (alreadyClaimed) return res.json({ alreadyClaimed: true });

  await db.claims.insert({ transactionId, userId: req.user.id });
  await grantCoins(req.user.id, verified.data.reward.value);
  res.json({ granted: true });
});

Native canvas / engine

The game client should never hold an API secret. Your backend creates the session, then hands the short-lived reward URL to the WebView:

POST /api/v1/sessions
Authorization: Basic base64(SITE_KEY:API_SECRET)
Content-Type: application/json
X-Site-Key: pk_live_...

{
  "externalUserId": "player_84721",
  "placement": "revive",
  "rewardLabel": "One revive",
  "customParameters": { "chapter": 4 }
}

Open the returned rewardUrl in a trusted WebView. The URL contains an opaque session token, never a CPX link. Poll the session status on your backend, or wait for your postback. Never grant a reward from a deep-link callback alone.

6. Integrating a website

Public-mode browser integrations use the site key and an allowed Origin. Origin is a browser policy check, not authentication: if the integration has an API secret, sessions require a short-lived signed site token. Mint it from your server and pass it to the SDK as sessionToken; never embed the API secret in page JavaScript.

// Your server (Basic site credentials)
POST /api/v1/site-tokens
Authorization: Basic base64(SITE_KEY:API_SECRET)

// Response: { "token": "...", "expiresIn": 300 }

Site tokens are bearer tokens with a five-minute TTL and are bound to the site's current API-secret version. Rotating the API secret immediately invalidates every outstanding token. They are not one-time tokens: reuse within the TTL is bounded by the same per-site and per-user quotas. Send the token in X-Site-Token, never in a URL or log. The SDK script is served with Cross-Origin-Resource-Policy: cross-origin; API preflight is generic because browsers omit the site key on OPTIONS, but every actual request still enforces site key, allowed origin and token.

<button id="unlock">Unlock the calculator</button>
<script src="https://ads.gptbuilder.space/sdk.js"></script>
<script>
  const rewards = new SurveyRewards({
    siteKey: 'pk_live_your_public_site_key',
    sessionToken: window.SURVEY_SITE_TOKEN, // from your server; omit in public mode
    externalUserId: window.APP_USER_ID,
    rewardLabel: 'Premium calculator access'
  });

  document.querySelector('#unlock').addEventListener('click', async () => {
    try {
      await rewards.openRewardGate({
        placement: 'premium_calculator',
        onComplete(result) {
          // Call your server; do not trust this callback alone.
          window.location = '/checkout/claim?session=' + encodeURIComponent(result.sessionId);
        }
      });
    } catch (error) {
      showError(error.message);
    }
  });
</script>

Your claim endpoint should call the verification API, then issue access exactly once per transaction. If you use cookies for the claim, protect it against CSRF on your own application as well.

7. Passing user IDs and custom parameters

externalUserId must be stable for the same player and unique within your integration. CPX uses it to build a survey profile and our callbacks use it to resolve the end user. Do not use emails, secrets or raw personal identifiers.

  • 1–128 printable characters.
  • Never change the ID for an existing player; profiles and matching quality depend on stability.
  • Use the same ID for every session and every reward action.
  • Custom parameters support strings, finite numbers and booleans; limit keys to 20 and values to 200 characters.
new SurveyRewards({
  siteKey: 'pk_live_...',
  externalUserId: 'player_84721',
  customParameters: {
    current_level: 7,
    platform: 'ios',
    acquisition: 'summer_campaign'
  }
});

In postback field mapping, reference these values with tokens such as {custom.current_level}. Avoid personal data in custom parameters.

8. Configuring postbacks

Postbacks are server-to-server notifications that a verified conversion changed state. Configure them per integration in the publisher dashboard or admin console.

Supported configuration

  • Method: GET or POST.
  • Formats: JSON, application/x-www-form-urlencoded, or query parameters.
  • Field mapping: rename every output field and map custom parameters.
  • Static fields: include fixed values such as an environment marker.
  • Secret: your chosen HMAC key, encrypted at rest and never returned by the API.
  • Events: conversion.created, conversion.confirmed, conversion.reversed.
  • Retries: 1–10 attempts with exponential backoff starting at 30 seconds (configurable), capped at one hour.

Default JSON payload

{
  "event": "conversion.created",
  "transaction_id": "conv_1f0c...a29",
  "user_id": "player_84721",
  "publisher_id": "firebase-uid",
  "site_id": "a1b2c3d4e5f6",
  "revenue": 0.81,
  "reward": "50",
  "currency": "USD",
  "status": "pending",
  "conversion_status": "pending",
  "session_id": "9ac2...",
  "survey_id": "484727",
  "cpx_transaction_id": "cpx-transaction-id",
  "created_at": "2026-11-13T12:00:00.000Z",
  "timestamp": "2026-11-13T12:00:00.000Z"
}

revenue is the publisher's earnings for that event in USD. Platform margin and CPX gross revenue are not exposed. Reversal events use the original transaction_id, set status to reversed, and include reversed_at. Deduplicate by X-GBA-Idempotency-Key or the event + transaction ID.

Available mapping tokens

TokenValue
{event}conversion.created, conversion.confirmed or conversion.reversed
{transaction_id}Stable platform conversion transaction ID
{cpx_transaction_id}CPX's transaction ID
{original_transaction_id}Original transaction for reversal events
{user_id}Publisher external user ID
{publisher_id} / {site_id}Publisher and integration IDs
{session_id} / {survey_id}Reward session and CPX survey ID
{revenue} / {revenue_cents}Publisher earnings in USD / integer cents
{reward} / {reward_value} / {reward_currency}Reward display values passed at session creation
{status} / {conversion_status}pending, confirmed or reversed
{created_at} / {confirmed_at} / {reversed_at} / {hold_until}ISO timestamps
{timestamp}Event timestamp used in the signature
{signature} / {delivery_id} / {attempt}Signature, delivery ID and attempt number
{custom.any_key}Custom parameter from the reward session

Signature verification

When enabled, every request includes:

X-GBA-Signature: sha256=<hex hmac>
X-GBA-Event: conversion.created
X-GBA-Transaction: conv_1f0c...a29
X-GBA-Timestamp: 2026-11-13T12:00:00.000Z
X-GBA-Idempotency-Key: conversion.created:conv_1f0c...a29
X-GBA-Delivery: pb_...

The signed canonical string is exactly:

event + "\n" + transaction_id + "\n" + timestamp

That string identifies the event but not its contents, so every signed delivery also carries a payload-binding signature:

X-GBA-Content-SHA256: <sha256 of the exact bytes on the wire>
X-GBA-Signature-V2: sha256=<hmac>

v2 + "\n" + event + "\n" + transaction_id + "\n" + timestamp + "\n" + contentSha256

For POST deliveries the content is the exact request body. For GET/query deliveries it is the raw query string after ?, exactly as sent. Verify v2 before trusting revenue, user_id or any other field; the v1 header is retained for compatibility.

Use the event’s timestamp from the payload, not the time the request arrived. Here is a complete Express receiver:

import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/postbacks/gptbuilder', (req, res) => {
  const signature = String(req.get('X-GBA-Signature') || '').replace(/^sha256=/, '');
  const event = req.body.event;
  const transactionId = req.body.transaction_id;
  const timestamp = req.body.timestamp;

  const expected = crypto
    .createHmac('sha256', process.env.GPTBUILDER_POSTBACK_SECRET)
    .update(`${event}\n${transactionId}\n${timestamp}`)
    .digest('hex');

  const a = Buffer.from(signature, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Idempotency: insert idempotency key with a unique constraint.
  const inserted = await db.deliveries.insertIfAbsent({
    idempotencyKey: req.get('X-GBA-Idempotency-Key')
  });
  if (!inserted) return res.status(200).json({ ok: true, duplicate: true });

  if (event === 'conversion.reversed') {
    // Revoke the reward in your own system if policy requires it.
  }

  res.status(200).json({ ok: true });
});
Retries are at-least-once

Your receiver must be idempotent. Return any 2xx promptly. Non-2xx responses and timeouts are retried with exponential backoff and logged in the dashboard; after max attempts the delivery is marked failed.

9. Receiving & verifying conversions

There are two independent notification channels: your configured postback endpoint and the pull-based verification API. Use both where appropriate: postbacks for asynchronous credit, verification before granting a reward.

Verify by session ID

curl -X POST https://ads.gptbuilder.space/api/v1/conversions/verify \
  -u "pk_live_your_site_key:your_api_secret" \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"9ac2d3..."}'

Response when verified:

{
  "ok": true,
  "data": {
    "verified": true,
    "status": "pending",
    "transactionId": "conv_1f0c...",
    "cpxTransactionId": "...",
    "sessionId": "9ac2d3...",
    "externalUserId": "player_84721",
    "publisherEarningsCents": 81,
    "currency": "USD",
    "reward": { "label": "50 gold coins", "value": "50", "currency": "coins" },
    "createdAt": "2026-11-13T12:00:00.000Z",
    "confirmedAt": null,
    "reversedAt": null,
    "holdUntil": "2026-11-20T12:00:00.000Z",
    "verification": "server_verified_cpx_postback"
  }
}

status: pending or held still means the completion is verified and the reward may be granted. Earnings remain pending for the platform hold period and can be reversed by CPX. held withholds publisher earnings for fraud review and is excluded from automatic maturity; an admin must release it before it can become confirmed. Treat reversals as a separate event.

Verify by transaction ID

GET /api/v1/conversions/conv_1f0c... HTTP/1.1
Host: ads.gptbuilder.space
Authorization: Basic base64(site_key:api_secret)

Authentication

Server calls require your site key and secret using HTTP Basic or the explicit headers:

Authorization: Basic base64(SITE_KEY:API_SECRET)
# or
X-Site-Key: pk_live_...
X-Site-Secret: your-server-secret

The key identifies the site, so a secret for one integration cannot verify another site's conversions.

Reversal status

If a conversion is reversed, verification returns verified: false and status: reversed. Do not grant or re-grant the reward based on an old callback. Store the transaction ID in your ledger and make your reward grant idempotent.

10. Handling reversals

CPX can later invalidate a completion. The platform handles this by appending a reversal transaction to the immutable ledger, moving publisher earnings out of pending or available, and sending a conversion.reversed postback. The session becomes reversed and the reward page will not show a reward.

Your responsibilities

  • Deduplicate reversal notifications by transaction_id + event.
  • Decide whether to claw back an in-game reward; do not double-debit if you receive a retry.
  • Return 2xx promptly. Failed deliveries are retried.
  • Do not treat a reversal as a new completion.

Publisher-initiated reversal API

If your own fraud review invalidates a completion, you can append a reversal with a reason. This is idempotent and site-scoped:

curl -X POST https://ads.gptbuilder.space/api/v1/conversions/conv_1f0c.../reverse \
  -u "pk_live_your_site_key:your_api_secret" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Duplicate account reward claim"}'

The response reports whether the operation was new or a duplicate. All reversals are auditable. Use this only for genuine invalid traffic or reward abuse; CPX chargebacks are handled automatically.

Hold periods

New conversions start as pending. After the configured hold period they become confirmed and publisher earnings move from pending to available. A conversion flagged by velocity limits, an inactive integration or a revenue anomaly starts as held instead: user reward verification still succeeds, but the earnings require an explicit admin release. A reversal before or after confirmation is recorded accurately, and an approved withdrawal can result in a negative available balance until future earnings recover it.

11. Testing integrations

Automated local tests

npm test                 # unit/API/security suite
npm run test:emulator    # real Firestore emulator (Java + firebase-tools)
npm run test:browser     # separate-origin Chrome test (CHROME_PATH optional)
npm run test:all         # all three

These suites are local checks. They do not prove live CPX parameter compatibility, a real Firebase project's behavior, or Vercel function-duration/cron behavior; verify those with a controlled Preview deployment and one real CPX callback.

Integration checklist

  • Site ownership file returns the exact token.
  • Create a session from an allowed origin and from your server with Basic auth.
  • Confirm the reward gate explains the reward before launching.
  • Confirm Continue without reward never grants anything.
  • Configure a test postback receiver and send a test from the dashboard.
  • Return 200, then return 500 and verify retry attempts appear.
  • Call the verification API with a correct secret and with a wrong secret.
  • Replay a completion callback; verify no second transaction or reward is created.
  • Enable test surveys, complete one from the reward gate, and verify isTest: true with zero earnings.
  • Enable a video ad, confirm normal-speed playback and early-completion rejection, then verify isVideoAd: true with zero earnings.
  • Reverse a test conversion and verify the reversal notification and reward UI.

Test postbacks

Use Send test postback on an integration. The platform creates a synthetic delivery with test_user and no financial effect. It uses the real endpoint and signing secret so you can validate parsing, HMAC and response handling before a live completion.

Testing CPX delivery

Admin testing can verify the CPX connection without crediting a transaction. A CPX callback with the optional test flag is acknowledged and logged but never creates a financial event. The explicit test-survey feature creates a labelled zero-value conversion for UI/integration testing; it is not a CPX completion and never moves money. Only CPX's signed callback creates a payable conversion.

Local development

Use a tunnel (for example, a secure HTTPS test domain) for postback receivers. Notifications must reach a public URL; localhost destinations are blocked to prevent SSRF. For SDK testing, the site URL or an added allowed origin must match the browser origin.

12. API reference

Base URL: https://ads.gptbuilder.space. All responses are JSON with an ok boolean. Errors use { ok: false, error: { code, message, details? } }. Authenticated dashboard calls use an opaque httpOnly session cookie and an X-CSRF-Token header for writes. Server integration calls use Basic site credentials.

SDK / integration endpoints

Method & pathAuthPurpose
POST /api/v1/site-tokensBasic site credentialsMint a five-minute site-scoped token for signed browser sessions.
POST /api/v1/sessionsSite key + signed token and Origin, or BasicCreate a reward session and return qualifying survey summaries plus a token.
GET /api/v1/sessions/:id?token=…Site key + signed token and Origin, or BasicRead session and server-verified status.
POST /api/v1/conversions/verifyBasic site credentialsVerify a completion by sessionId or transactionId.
GET /api/v1/conversionsBasic site credentialsList conversions for externalUserId (max 100).
GET /api/v1/conversions/:idBasic site credentialsRead one conversion.
POST /api/v1/conversions/:id/reverseBasic site credentialsAppend a publisher-initiated reversal with a reason.

Create session

POST /api/v1/sessions
X-Site-Key: pk_live_...
Origin: https://your-approved-domain.example
Content-Type: application/json

{
  "externalUserId": "player_84721",
  "placement": "level_7_unlock",
  "rewardLabel": "50 gold coins",
  "rewardValue": "50",
  "rewardCurrency": "coins",
  "customParameters": { "current_level": 7 }
}
{
  "ok": true,
  "data": {
    "sessionId": "9ac2d3...",
    "token": "opaque-session-token",
    "rewardUrl": "https://ads.gptbuilder.space/reward?session=9ac2d3...&token=opaque-session-token",
    "status": "active",
    "expiresAt": "2026-11-13T14:00:00.000Z",
    "inventoryStatus": "ready",
    "surveys": [
      { "id": "484727", "loiMinutes": 2, "score": 18.1, "rating": 4.4, "ratingCount": 91, "conversionRate": 89, "top": true, "needsQualification": false }
    ],
    "fallback": { "available": true, "videoUrl": "https://...", "rewarded": false },
    "reward": { "label": "50 gold coins", "value": "50", "currency": "coins" }
  }
}

Sites with an API secret must also send X-Site-Token, minted from your server as described above. Basic site credentials can create sessions server-to-server without an Origin.

Survey href links are never included. They exist only inside the first-party reward page, preventing publishers or users from harvesting user-scoped CPX links. rewardUrl is the short-lived first-party reward gate to open in a WebView or browser; treat it like a bearer token.

Publisher dashboard endpoints

Method & pathPurpose
POST /api/auth/sessionExchange a Firebase ID token for an opaque session (sets cookies).
POST /api/auth/publisher-applicationSubmit the first publisher and integration application.
GET /api/publishers/overviewBalances, stats, recent transactions, sites and payout settings.
POST /api/publishers/sitesSubmit an additional integration for review.
POST /api/publishers/sites/:id/api-secretSet or rotate the server API secret (stored as a scrypt hash).
POST /api/publishers/sites/:id/verifyCheck the well-known ownership file.
PUT /api/publishers/sites/:id/postbackConfigure endpoint, mapping, HMAC and events.
POST /api/publishers/sites/:id/postback/testSend a synthetic signed delivery.
GET /api/publishers/transactionsPublisher ledger entries.
POST /api/publishers/withdrawalsReserve available earnings and request a payout.

Admin endpoints

All admin routes require an authenticated administrator with a verified email. Key routes include /api/admin/applications/:siteId/decision, /api/admin/publishers/:id, /api/admin/sites/:id, /api/admin/transactions/:id/action, /api/admin/withdrawals/:id/decision, /api/admin/postbacks/test, /api/admin/finance, /api/admin/settings, /api/admin/fraud-flags/:id, /api/admin/audit-logs and DELETE /api/admin/end-users/:id for identifier erasure.

Admin settings cover session quotas, velocity limits, alert webhook, retention periods, hold period, revenue shares and payout minimums. Held conversions require an explicit release; release is audited and idempotent.

System endpoints

Method & pathPurpose
GET /api/healthMinimal service status (version, time and database reachability; no configuration details).
GET /api/configPublic platform name and maintenance state.
GET|POST /api/cpx/postbackCPX server-to-server callback; MD5 secure hash required.
GET /api/internal/cronDaily protected job: maturity, retries and cleanup. Requires Authorization: Bearer CRON_SECRET.

Rate limits

General API requests are limited per IP, with tighter limits on session creation, verification and reward actions. Sites also have atomic per-minute, per-hour and per-day session quotas plus a per-user hourly quota. Configure them in admin settings; a limit of 0 disables that window. A quota rejection returns 429 session_quota_exceeded with Retry-After. Responses include RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Callback traffic has a separate, higher limit.

CPX integration details

The platform uses CPX Research's documented user-based survey API. It sends only documented parameters; live CPX compatibility must be verified with a controlled callback, not assumed from local tests:

GET https://live-api.cpx-research.com/api/get-surveys.php
  ?app_id={app_id}
  &ext_user_id={cpx_user_id}
  &subid_1={reward_session_id}
  &subid_2={site_id}
  &output_method=api
  &ip_user={user_ip}
  &user_agent={user_agent}
  &limit=12
  &secure_hash={md5(ext_user_id + "-" + secure_hash)}
  • Survey requests are user-scoped and cached for no longer than 120 seconds. Links are never shared across users.
  • Surveys below the configured minimum publisher payout (default $0.10) are excluded.
  • Ranking prioritises CPX quality score, a 1–2 minute completion preference, top status, user ratings and shorter LOI.
  • No surveys → optional non-rewarded video fallback; no earnings or reward are recorded.
  • CPX postbacks require the documented status (1 = completed, 2 = reversed), trans_id, user_id, amount_usd and hash = md5(trans_id + "-" + secure_hash).
  • Actual amount_usd is recorded as CPX revenue; estimated survey-list payouts are never treated as confirmed revenue.

CPX postback URL

In the CPX publisher dashboard set the postback URL to the exact macros CPX displays, mapped to our receiver. A typical configuration is:

https://ads.gptbuilder.space/api/cpx/postback
  ?status={status}
  &trans_id={trans_id}
  &user_id={user_id}
  &amount_local={amount_local}
  &amount_usd={amount_usd}
  &offer_id={offer_id}
  &hash={hash}
  &ip_click={ip_click}
  &subid_1={subid_1}
  &subid_2={subid_2}

Use the placeholder names CPX shows in its dashboard; if CPX provides {secure_hash} instead of {hash}, our receiver accepts either field name for the same documented hash. Do not add undocumented authentication parameters. Configure the CPX app ID and secure hash in the admin console or via environment variables.

Troubleshooting

SymptomLikely cause & fix
SDK returns “origin not allowed”Add the exact browser origin’s hostname as an allowed origin, or use the registered site URL’s domain. Include subdomains explicitly; www variants are handled.
SDK returns “site is not approved”The publisher or integration is pending, rejected, suspended or paused. Check status in the dashboard and maintenance mode in admin.
No qualifying surveysCPX may have no inventory for this user, or all surveys are below the payout threshold, filtered by rating, webcam filter or maximum LOI. The fallback is non-rewarded by design.
Reward never appearsCompletion is verified only by a signed CPX callback. Check the session status endpoint, CPX postback configuration and the admin CPX event log. A client redirect is not proof.
Postback gets 401Verify the HMAC using the exact canonical string and the raw secret you configured. Do not hash the URL or include newline differences.
Postback retries but then failsEndpoint returned non-2xx or timed out. Check delivery logs and URL reachability. Production postbacks must use HTTPS and public DNS.
API verification 401Use Basic siteKey:apiSecret for the matching site. If the secret was rotated, update the server environment.
API verification returns verified:falseNo CPX completion has arrived, the session expired, the transaction was reversed, or you are querying the wrong user's session.
SDK blocked by CORS or CORPUse the hosted SDK script and an exact allowed origin. API preflight permits the browser's OPTIONS request, but actual calls still require the site key and (when configured) a signed site token. Do not add a wildcard origin.
Signed site token rejected after rotationMint a new token after changing the API secret. Tokens are bound to the secret version and expire after five minutes; do not cache them longer than their TTL.
Session creation returns 403 signed_session_requiredThis integration has an API secret, so browser sessions need X-Site-Token. Mint one from your server with Basic credentials and pass it as sessionToken; do not put the API secret in the browser.
Session creation returns 429 session_quota_exceededThe site or user hit a configured session quota or IP rate limit. Respect Retry-After; review quotas in admin settings if legitimate traffic is affected.
Withdrawal requests blockedAvailable balance is below the minimum or email is unverified. Pending earnings are not withdrawable until the hold period passes.
Firebase Authentication errorsEnable Email/Password provider, add ads.gptbuilder.space to authorized domains, and verify the public Firebase web config matches the project.
Firestore permission deniedThe browser must never read Firestore directly. All reads/writes go through the API. Deploy firestore.rules and keep the Admin SDK credentials server-side.
CPX signature failuresConfirm the CPX App ID and Secure Hash belong to the same app. API calls use md5(ext_user_id + "-" + secure_hash); callbacks use md5(trans_id + "-" + secure_hash).

Operational notes

  • Sessions expire after two hours; a new reward action creates a new session.
  • Survey inventory is cached for at most 120 seconds.
  • Conversions become available after the configured hold period; the daily cron also retries due postbacks.
  • All money is stored and reported in integer cents. Platform margin is the difference between actual CPX revenue and publisher earnings.
  • IP rate limits are enforced in-process per serverless instance and absorb bursts; site and per-user session quotas are transactional and global. Pair them with platform-level bot/WAF controls for hostile traffic.
  • Postback retries are attempted immediately, by the daily cron inside a bounded time budget, and manually from admin. Deliveries that exceed the budget stay pending for the next run. Atomic delivery leases prevent duplicate sends when cron and immediate workers overlap.
  • High-severity events (ignored/unknown CPX callbacks, velocity or anomaly holds) can be sent to a public HTTPS alert webhook configured with ALERT_WEBHOOK_URL or in admin settings. Alerts are throttled to one per hour for the same type and transaction or reason.
  • Retention defaults: CPX callback events 90 days, audit logs 365 days and postback delivery logs 90 days; the cron also removes expired quota and alert-throttle counters. Adjust these in admin settings.
  • IP addresses, user agents and external user identifiers are kept only for session integrity, fraud review and ledger reconciliation. Admins can erase an end user; related financial records stay in redacted form for accounting.
  • Never expose the Firebase service account, CPX secure hash, credential encryption key, or postback signing secret to a browser.

Still stuck? Contact support@gptbuilder.space with the integration ID, session ID and approximate timestamp. Never send your API secret.