Integration Documentation
Technical guide for developers integrating VeloxOffer Offerwalls, REST Offer Feeds, and Server-to-Server (S2S) Postbacks.
1. Overview
Choose the integration method that fits your product:
Hosted Offerwall
Fastest setup. Embed with a single iFrame or mobile WebView. VeloxOffer handles offer display, tracking, and user flow.
REST API Feed
Full UI control. Query active offers as JSON and render your own custom offer cards, lists, or reward store.
S2S Postbacks
Instant HTTP webhook sent to your server whenever a user completes an offer to award virtual currency.
Credentials Reference
Copy these from your Publisher Dashboard:
| Key | Example | Where to Find |
|---|---|---|
| pub_id | 1042 |
Your numeric Publisher ID. Shown in dashboard header. |
| placement_id | 85 |
Numeric ID for your app or website placement. Found under Apps & Placements. |
| api_key / secret | sec_8f912c4b... |
Cryptographic key for iFrame access and postback signature verification. |
Base URL & Limits
Rate limit: 60 requests/minute per IP on the Feed API. Cache responses locally for 30–60 seconds.
2. Offerwall Integration
Embed our pre-built, responsive offerwall directly into your site or app.
<!-- Responsive Offerwall Embed -->
<iframe
src="https://veloxoffer.com/wall?api_key=YOUR_API_KEY&user_id=USER_12345"
style="width: 100%; height: 850px; border: none; border-radius: 8px;"
allow="geolocation; camera"
loading="lazy">
</iframe>
// Direct Offerwall Link (open in browser tab or external link) https://veloxoffer.com/wall?api_key=YOUR_API_KEY&user_id=USER_12345&sub_id=ad_campaign_1
URL Parameters
| Parameter | Type | Requirement | Description |
|---|---|---|---|
| api_key | string | Required | Your placement API Key from your dashboard. |
| user_id | string | Required | Unique identifier for your user. Echoed back in postbacks to credit points. |
| sub_id | string | Optional | Custom tracking sub-tag (e.g. acquisition channel). |
| sub_id2 | string | Optional | Secondary tracking sub-tag. |
Mobile WebView Implementation
Enable DOM Storage and JavaScript so interactive surveys and offers render properly:
// Android WebView Setup
val webView = findViewById<WebView>(R.id.offerwallWebView)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
setSupportMultipleWindows(true)
}
webView.webViewClient = WebViewClient()
val apiKey = "YOUR_API_KEY"
val userId = currentUser.id
webView.loadUrl("https://veloxoffer.com/wall?api_key=$apiKey&user_id=$userId")
// iOS WKWebView Setup
import WebKit
let config = WKWebViewConfiguration()
config.preferences.javaScriptEnabled = true
let webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
let apiKey = "YOUR_API_KEY"
let userId = "USER_12345"
if let url = URL(string: "https://veloxoffer.com/wall?api_key=\(apiKey)&user_id=\(userId)") {
webView.load(URLRequest(url: url))
}
3. Offers Feed API (REST JSON)
Query raw offer metadata to build a native in-app rewards list or custom offerwall UI.
Query Filters
| Parameter | Type | Status | Accepted Values |
|---|---|---|---|
| pub_id | integer | Required | Your numeric Publisher ID (e.g. 1042). |
| placement_id | integer | Optional | Applies your placement revenue share to calculated payouts. |
| country_name | string | Optional | 2-letter ISO country code (e.g. US, GB, DE). |
| device | string | Optional | Android, Iphone, Ipad, Windows, Mac. |
| browser | string | Optional | Chrome, Safari, Firefox, Edge. |
| category_id | integer | Optional | Filter by category ID (Games, Surveys, Free Trials). |
Sample Request & Response
curl -X GET "https://veloxoffer.com/api?pub_id=1042&placement_id=85&device=Android&country_name=US" -H "Accept: application/json"
[
{
"id": 1084,
"offer_name": "Hero Wars - Alliance: Reach Chapter 3",
"link": "https://veloxoffer.com/click?offer_id=1084&pub_id=1042&app_id=85",
"description": "Install and complete Chapter 3 within 14 days. New users only.",
"category_name": "Mobile Games",
"ua_target": "Android, Tablet",
"browsers": "Chrome, Edge",
"countries": "US, CA, GB, AU, DE",
"payout_type": "CPA",
"payout": 3.85,
"lead_qty": "Single"
}
]
4. Server-to-Server (S2S) Postbacks
Postbacks are real-time HTTP calls sent to your server when an offer is completed to award user points safely.
Macro Tokens Reference
Use bracketed tokens in your URL. VeloxOffer replaces them dynamically at dispatch:
| Token | Example | Description |
|---|---|---|
| {user_id} | usr_98234 |
User ID passed into the offerwall or tracking link. |
| {reward} / {amount} | 250 |
Points or coins to credit to the user based on placement exchange rate. |
| {payout} | 2.5000 |
Your revenue in USD ($). |
| {txn_id} | txn_660e12f9b8c3 |
Globally unique conversion ID. Use this to prevent duplicate credits. |
| {status} | 1 or 2 |
1 = Conversion Approved. 2 = Chargeback/Reversal (deduct points). |
| {currency} | Coins |
Your placement virtual currency name. |
| {offer_id} | 1084 |
Completed offer numeric ID. |
| {offer_name} | Hero Wars Alliance |
Campaign name. |
| {ip} | 198.51.100.42 |
User IP address at conversion time. |
| {timestamp} | 1777287361 |
UNIX timestamp. |
| {secret} | sec_8f912c4b... |
Your placement secret key for request validation. |
The 3 Golden Postback Rules
Deduplicate by txn_id
Always verify if txn_id was already processed in your DB. If it exists, return 1 immediately without adding points again.
Handle Reversals (status=2)
If status=2 is received, deduct the reward points from the user's balance due to fraud or chargeback.
Reply HTTP 200 "1"
Your server MUST reply with HTTP 200 OK and body 1. Any other status code triggers 5 automatic retries (10s, 30s, 1m, 5m, 15m).
Secret Key Verification
Verify that incoming postback requests originate from VeloxOffer:
<?php
$appSecret = "YOUR_PLACEMENT_SECRET_KEY";
$receivedSecret = $_GET['secret'] ?? '';
if (!hash_equals($appSecret, $receivedSecret)) {
http_response_code(403);
exit('Unauthorized secret key');
}
// Secret key verified. Proceed with credit...
// Node.js Secret Verification
const SECRET_KEY = process.env.VELOXOFFER_SECRET;
app.get('/postback', (req, res) => {
if (req.query.secret !== SECRET_KEY) {
return res.status(403).send('Unauthorized');
}
// Secret verified. Proceed with credit...
res.status(200).send('1');
});
# Python Secret Verification
import hmac
SECRET_KEY = "YOUR_PLACEMENT_SECRET_KEY"
def verify_request(secret_arg):
if not hmac.compare_digest(secret_arg, SECRET_KEY):
abort(403)
5. Ready-to-Use Webhook Code Recipes
Complete, runnable postback listeners with database deduplication and balance management:
<?php
/**
* VeloxOffer Production Postback Receiver
* URL: https://yoursite.com/postback.php?user_id={user_id}&reward={reward}&payout={payout}&txn_id={txn_id}&status={status}&offer_id={offer_id}&secret={secret}
*/
header('Content-Type: text/plain');
$SECRET_KEY = 'YOUR_PLACEMENT_SECRET_KEY';
// 1. Read parameters
$userId = $_GET['user_id'] ?? '';
$reward = floatval($_GET['reward'] ?? 0);
$payout = floatval($_GET['payout'] ?? 0);
$txnId = $_GET['txn_id'] ?? '';
$status = intval($_GET['status'] ?? 1); // 1 = Approved, 2 = Reversal
$offerId = $_GET['offer_id'] ?? '';
$secret = $_GET['secret'] ?? '';
// 2. Validate Secret
if (!hash_equals($SECRET_KEY, $secret)) {
http_response_code(403);
exit("Invalid secret key");
}
if (empty($userId) || empty($txnId)) {
http_response_code(400);
exit("Missing parameters");
}
// 3. Database connection & Deduplication
try {
$pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'db_user', 'db_pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
// Check if txn_id was already handled
$stmt = $pdo->prepare("SELECT id FROM transactions WHERE txn_id = ?");
$stmt->execute([$txnId]);
if ($stmt->fetch()) {
exit("1"); // Already processed, acknowledge receipt
}
// 4. Update user balance
if ($status === 1) {
$pdo->beginTransaction();
$pdo->prepare("UPDATE users SET points = points + ? WHERE id = ?")->execute([$reward, $userId]);
$pdo->prepare("INSERT INTO transactions (user_id, txn_id, offer_id, reward, payout, status, created_at) VALUES (?, ?, ?, ?, ?, 'approved', NOW())")
->execute([$userId, $txnId, $offerId, $reward, $payout]);
$pdo->commit();
} elseif ($status === 2) {
$pdo->beginTransaction();
$pdo->prepare("UPDATE users SET points = GREATEST(0, points - ?) WHERE id = ?")->execute([$reward, $userId]);
$pdo->prepare("INSERT INTO transactions (user_id, txn_id, offer_id, reward, payout, status, created_at) VALUES (?, ?, ?, ?, ?, 'reversed', NOW())")
->execute([$userId, $txnId, $offerId, -$reward, -$payout]);
$pdo->commit();
}
// 5. Must return 1
echo "1";
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
http_response_code(500);
echo "Error: " . $e->getMessage();
}
const express = require('express');
const app = express();
const SECRET_KEY = process.env.VELOXOFFER_SECRET_KEY;
app.get('/postback', async (req, res) => {
const { user_id, reward, payout, txn_id, status, secret } = req.query;
if (secret !== SECRET_KEY) {
return res.status(403).send('Unauthorized');
}
try {
// Prevent duplicate processing
const alreadyCredited = await db.checkTxn(txn_id);
if (alreadyCredited) {
return res.status(200).send('1');
}
if (parseInt(status) === 1) {
await db.addPoints(user_id, parseFloat(reward), txn_id);
} else if (parseInt(status) === 2) {
await db.deductPoints(user_id, parseFloat(reward), txn_id);
}
return res.status(200).send('1');
} catch (err) {
console.error('Postback error:', err);
return res.status(500).send('Server error');
}
});
app.listen(3000, () => console.log('Postback listener active on port 3000'));
from flask import Flask, request, abort
import hmac
app = Flask(__name__)
SECRET_KEY = "YOUR_PLACEMENT_SECRET_KEY"
@app.route('/postback', methods=['GET'])
def handle_postback():
secret = request.args.get('secret', '')
if not hmac.compare_digest(secret, SECRET_KEY):
abort(403)
user_id = request.args.get('user_id')
reward = float(request.args.get('reward', 0))
txn_id = request.args.get('txn_id')
status = int(request.args.get('status', 1))
if db_has_txn(txn_id):
return "1", 200
if status == 1:
credit_user(user_id, reward, txn_id)
elif status == 2:
deduct_user(user_id, reward, txn_id)
return "1", 200
if __name__ == '__main__':
app.run(port=5000)
6. Pre-Flight Checklist
| # | Verification Item | Status |
|---|---|---|
| 1 | Pass a non-empty, unique user_id for every user in the offerwall URL. |
Mandatory |
| 2 | Your postback endpoint responds with HTTP 200 OK and body 1. |
Mandatory |
| 3 | Your database verifies txn_id uniqueness to prevent double crediting. |
Mandatory |
| 4 | Your script deducts points when status=2 (chargeback/reversal). |
Mandatory |
| 5 | Offers API Feed queries are cached for 30–60 seconds to prevent rate limits. | Recommended |
Developer Support
Partner Support
Direct Telegram or Slack engineering channels for certified partners.
Contact Partner Support →