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
- Your server or browser SDK creates a short-lived reward session for a user.
- The reward gate displays the reward and one qualifying survey. Survey links stay on the GPTBuilder Ads domain.
- CPX sends a completion to
/api/cpx/postbackwith the documented MD5 hash. - Our server records a pending conversion in the immutable ledger and notifies your configured postback endpoint.
- Your server calls the verification API with its site secret, then grants the reward exactly once.
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.
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
| Option | Required | Description |
|---|---|---|
siteKey | Yes | Public site key from the dashboard. It identifies the approved integration. |
externalUserId | Yes | Stable ID for the player in your product. Use your internal user ID, not an email. |
rewardLabel | No | Human-readable reward name shown before the survey. |
rewardValue / rewardCurrency | No | Display values for the reward. The platform does not convert or grant game currency itself. |
customParameters | No | Flat object of strings, numbers or booleans. Max 20 keys, 200 chars per value. Available in postbacks as {custom.key}. |
sessionToken | Signed mode | Five-minute site token minted by your server from POST /api/v1/site-tokens. Required when the site has an API secret. |
apiBase | No | Defaults 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.
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.
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 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
- Choose a reward-required action, such as a revive, level unlock, or currency top-up.
- Call
openRewardGatefrom a direct user gesture. - Do not apply the reward in
onCompleteuntil your server verifies it. - On your backend, call
POST /api/v1/conversions/verifywith the session ID. - 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
| Token | Value |
|---|---|
{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 });
});
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: truewith zero earnings. - Enable a video ad, confirm normal-speed playback and early-completion rejection, then verify
isVideoAd: truewith 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 & path | Auth | Purpose |
|---|---|---|
POST /api/v1/site-tokens | Basic site credentials | Mint a five-minute site-scoped token for signed browser sessions. |
POST /api/v1/sessions | Site key + signed token and Origin, or Basic | Create a reward session and return qualifying survey summaries plus a token. |
GET /api/v1/sessions/:id?token=… | Site key + signed token and Origin, or Basic | Read session and server-verified status. |
POST /api/v1/conversions/verify | Basic site credentials | Verify a completion by sessionId or transactionId. |
GET /api/v1/conversions | Basic site credentials | List conversions for externalUserId (max 100). |
GET /api/v1/conversions/:id | Basic site credentials | Read one conversion. |
POST /api/v1/conversions/:id/reverse | Basic site credentials | Append 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 & path | Purpose |
|---|---|
POST /api/auth/session | Exchange a Firebase ID token for an opaque session (sets cookies). |
POST /api/auth/publisher-application | Submit the first publisher and integration application. |
GET /api/publishers/overview | Balances, stats, recent transactions, sites and payout settings. |
POST /api/publishers/sites | Submit an additional integration for review. |
POST /api/publishers/sites/:id/api-secret | Set or rotate the server API secret (stored as a scrypt hash). |
POST /api/publishers/sites/:id/verify | Check the well-known ownership file. |
PUT /api/publishers/sites/:id/postback | Configure endpoint, mapping, HMAC and events. |
POST /api/publishers/sites/:id/postback/test | Send a synthetic signed delivery. |
GET /api/publishers/transactions | Publisher ledger entries. |
POST /api/publishers/withdrawals | Reserve 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 & path | Purpose |
|---|---|
GET /api/health | Minimal service status (version, time and database reachability; no configuration details). |
GET /api/config | Public platform name and maintenance state. |
GET|POST /api/cpx/postback | CPX server-to-server callback; MD5 secure hash required. |
GET /api/internal/cron | Daily 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_usdandhash = md5(trans_id + "-" + secure_hash). - Actual
amount_usdis 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
| Symptom | Likely 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 surveys | CPX 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 appears | Completion 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 401 | Verify 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 fails | Endpoint returned non-2xx or timed out. Check delivery logs and URL reachability. Production postbacks must use HTTPS and public DNS. |
| API verification 401 | Use Basic siteKey:apiSecret for the matching site. If the secret was rotated, update the server environment. |
| API verification returns verified:false | No 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 CORP | Use 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 rotation | Mint 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_required | This 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_exceeded | The 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 blocked | Available balance is below the minimum or email is unverified. Pending earnings are not withdrawable until the hold period passes. |
| Firebase Authentication errors | Enable Email/Password provider, add ads.gptbuilder.space to authorized domains, and verify the public Firebase web config matches the project. |
| Firestore permission denied | The 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 failures | Confirm 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_URLor 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.