Building a custom offerwall using a CPA network's API gives you maximum control over user experience, offer curation, and revenue optimization — and it eliminates the revenue share that third-party offerwall SDKs charge. If you are a developer building a rewards site, GPT platform, or mobile app with offerwall monetization, API integration is the path to the best economics and the best user experience.
This guide is written for developers. It covers the technical architecture of offerwall API integration: fetching offers, displaying them, handling click tracking, building a postback receiver, crediting user accounts, implementing security measures, and testing the entire flow before production deployment. Code examples are provided in pseudocode that translates easily to any language or framework.
Offerwall API Architecture Overview
An offerwall API integration has four main components that work together. The architecture follows standard RESTful API patterns as described by resources like Schema.org for data structuring and the IAB Tech Lab for ad measurement standards:
1. Offer Feed API
An HTTP endpoint provided by the CPA network that returns a list of available offers. You call this API periodically (or on demand) to get offer data: titles, descriptions, payouts, tracking URLs, requirements, and targeting criteria.
2. Click Tracking
When a user clicks an offer on your offerwall, you redirect them to the network's tracking URL. This URL contains your publisher ID, the offer ID, and a sub-ID parameter carrying the user's identifier on your platform. The network records the click and redirects the user to the advertiser's page.
3. Postback Handler
A server endpoint on your infrastructure that receives HTTP requests from the CPA network when a user completes an offer. The postback carries the user ID, offer details, payout amount, and transaction ID. Your handler processes this data and credits the user.
4. User Reward System
Your application logic that translates CPA payouts into in-app rewards (points, coins, balance) and credits the appropriate user account.
Data Flow Diagram
User → Your Offerwall UI → Click → Network Tracking URL → Advertiser Page
↓
User completes offer
↓
Advertiser confirms conversion
↓
Network fires postback to your server
↓
Your server validates and credits user
Step 1: Set Up Your Development Environment
Obtain API Credentials
Sign up as a publisher with the CPA network and request API access. You will typically receive:
- API key or token — Used to authenticate your API requests
- Publisher ID — Your unique identifier in the network's system
- API documentation — Endpoints, parameters, response formats, and rate limits
- Postback token/secret — Used to validate incoming postback requests
- Test environment details — Sandbox or test mode credentials (if available)
Database Schema
You need tables (or collections) for at minimum these data entities:
-- Offers table: cached offer data from the API
offers
id (PK)
network_offer_id (string, indexed)
title (string)
description (text)
payout (decimal)
user_reward (decimal)
tracking_url (string)
requirements (text)
category (string)
allowed_geos (string/json)
allowed_devices (string/json)
status (enum: active, paused)
daily_cap (integer, nullable)
daily_conversions (integer, default 0)
last_synced_at (timestamp)
created_at (timestamp)
updated_at (timestamp)
-- Conversions table: postback records
conversions
id (PK)
transaction_id (string, unique, indexed)
user_id (FK to users, indexed)
offer_id (FK to offers)
network_offer_id (string)
payout (decimal)
user_reward (decimal)
status (enum: pending, approved, reversed)
ip_address (string)
raw_postback (text)
created_at (timestamp)
updated_at (timestamp)
-- Users table (assuming you have one)
users
id (PK)
email (string)
balance (decimal, default 0)
lifetime_earnings (decimal, default 0)
...
Technology Requirements
- A web server capable of handling HTTP requests (Node.js, Python/Django, PHP/Laravel, Ruby/Rails, Go, etc.)
- A database (PostgreSQL, MySQL, MongoDB)
- HTTPS certificate for your postback endpoint
- A caching layer (Redis, Memcached) for offer data is recommended but not required
- A job queue (for asynchronous processing) is recommended for production
Step 2: Fetch and Cache Offers
Calling the Offer Feed API
The offer feed API typically accepts these parameters:
| Parameter | Description | Example |
|---|---|---|
api_key | Your authentication token | abc123def456 |
publisher_id | Your publisher ID | 789 |
country | Filter offers by geo (optional) | US |
platform | Filter by device platform (optional) | mobile, desktop, all |
category | Filter by offer category (optional) | finance, health |
format | Response format | json |
Example API Request (Pseudocode)
response = http.get("https://api.cpanetwork.com/v1/offers", {
headers: { "Authorization": "Bearer YOUR_API_KEY" },
params: {
publisher_id: "789",
country: "US",
platform: "all",
format: "json"
}
})
offers = response.json().data // Array of offer objects
Typical Offer Object Structure
{
"offer_id": "1042",
"title": "BudgetPro - Free Budget App",
"description": "Sign up for a free BudgetPro account and link a bank account.",
"payout": 5.00,
"category": "finance",
"tracking_url": "https://track.network.com/click?oid=1042&pid=789&sub1={sub1}&sub2={sub2}",
"requirements": "User must sign up and link at least one bank account. US only.",
"allowed_traffic": ["incent", "web", "social"],
"allowed_geos": ["US"],
"allowed_devices": ["mobile", "desktop"],
"daily_cap": 500,
"epc": 0.85,
"conversion_rate": 0.17,
"status": "active"
}
Caching Strategy
Do not call the offer API on every page load. Implement a caching layer:
- Sync frequency — Pull the full offer feed every 15-60 minutes via a cron job or background task.
- Store in database — Save offer data to your offers table. Update existing records, add new ones, and mark removed offers as paused.
- Cache in memory — For high-traffic offerwalls, cache the formatted offer list in Redis with a 5-15 minute TTL for fast page loads.
- Handle API failures gracefully — If the API is down, serve offers from your local cache. Do not show an empty offerwall.
Calculating User Rewards
When syncing offers, calculate and store the user reward amount based on your payout share percentage:
user_reward_share = 0.70 // 70% to user, 30% margin for you
for each offer in api_response:
offer.user_reward = round(offer.payout * user_reward_share, 2)
// $5.00 payout * 0.70 = $3.50 user reward
save_to_database(offer)
Step 3: Build the Offerwall Display
Serving Offers to Users
Create an API endpoint or page that returns the list of offers appropriate for the current user. Filter based on:
- Geo — Only show offers targeting the user's country (determine via IP geolocation)
- Device — Only show mobile offers on mobile devices, desktop offers on desktop
- Cap status — Hide offers that have reached their daily conversion cap
- Already completed — Optionally hide offers the user has already converted on (check your conversions table)
Sorting and Ranking
How you sort offers impacts revenue significantly. Effective sorting strategies:
- By eCPM/EPC — Highest-earning offers first (maximizes your revenue)
- By user reward — Highest rewards first (maximizes user perception of value)
- By conversion rate — Easiest offers first (maximizes completion rate, good for new users)
- Weighted score — Combine EPC, conversion rate, and user reward into a composite score. This usually produces the best overall results.
// Example weighted scoring
for each offer:
offer.score = (offer.epc * 0.4) + (offer.conversion_rate * 0.3) + (offer.user_reward * 0.3)
sort(offers, by: score, descending)
Generating Tracked Offer Links
When displaying offers, generate tracked click URLs that include the user's ID:
// Replace {sub1} token with the user's ID
function get_offer_link(offer, user_id):
url = offer.tracking_url
url = url.replace("{sub1}", user_id)
url = url.replace("{sub2}", offer.internal_id) // optional: your internal offer reference
return url
The sub1 parameter is critical — it is how you identify which user completed the offer when the postback fires. Without it, you cannot credit the correct user.
Step 4: Build the Postback Handler
The postback handler is the most important piece of your integration. It must be reliable, secure, and fast. Read our full postback tracking setup guide for additional context.
Postback URL Configuration
In your CPA network's dashboard, configure your postback URL:
https://yourdomain.com/api/postback?user_id={sub1}&offer_id={offer_id}&payout={payout}&tx_id={transaction_id}&status={status}&secret=YOUR_SECRET
Handler Implementation (Pseudocode)
function handle_postback(request):
// 1. Parse parameters
user_id = request.param("user_id")
offer_id = request.param("offer_id")
payout = float(request.param("payout"))
tx_id = request.param("tx_id")
status = request.param("status") // "approved", "pending", "reversed"
secret = request.param("secret")
source_ip = request.ip
// 2. Validate secret
if secret != ENV["POSTBACK_SECRET"]:
log_warning("Invalid secret from IP: " + source_ip)
return response(403, "Forbidden")
// 3. Validate source IP (optional but recommended)
if source_ip not in ALLOWED_NETWORK_IPS:
log_warning("Unexpected IP: " + source_ip)
return response(403, "Forbidden")
// 4. Validate required fields
if not all([user_id, offer_id, payout, tx_id]):
log_error("Missing required fields")
return response(400, "Bad Request")
// 5. Check for duplicate
existing = db.find("conversions", where: { transaction_id: tx_id })
if existing:
if status == "reversed" and existing.status != "reversed":
// Handle reversal of previously approved conversion
handle_reversal(existing, user_id)
return response(200, "Reversal processed")
log_info("Duplicate postback: " + tx_id)
return response(200, "Already processed")
// 6. Look up the user
user = db.find("users", where: { id: user_id })
if not user:
log_error("Unknown user: " + user_id)
return response(200, "OK") // Return 200 to prevent retries
// 7. Calculate user reward
offer = db.find("offers", where: { network_offer_id: offer_id })
if offer:
user_reward = offer.user_reward
else:
user_reward = round(payout * 0.70, 2) // Fallback: 70% share
// 8. Record the conversion
db.insert("conversions", {
transaction_id: tx_id,
user_id: user_id,
offer_id: offer_id,
network_offer_id: offer_id,
payout: payout,
user_reward: user_reward,
status: status,
ip_address: source_ip,
raw_postback: request.full_url,
created_at: now()
})
// 9. Credit the user (if approved)
if status == "approved":
db.increment("users", user_id, "balance", user_reward)
db.increment("users", user_id, "lifetime_earnings", user_reward)
// Optionally: send notification to user
notify_user(user_id, "You earned $" + user_reward + " from " + offer.title)
// 10. Return success
log_info("Postback processed: " + tx_id + " for user: " + user_id)
return response(200, "OK")
Handling Reversals
When a CPA network reverses a conversion (advertiser rejected it), you receive a postback with a "reversed" status. You must deduct the previously credited reward:
function handle_reversal(existing_conversion, user_id):
if existing_conversion.status == "approved":
db.decrement("users", user_id, "balance", existing_conversion.user_reward)
db.update("conversions", existing_conversion.id, { status: "reversed" })
log_info("Reversed conversion: " + existing_conversion.transaction_id)
Step 5: Security Implementation
An unsecured postback endpoint is a critical vulnerability. Implement all of these security measures:
IP Whitelisting
Maintain a list of IP addresses that your CPA network uses to send postbacks. Reject requests from any other IP. Most networks publish their server IPs in their documentation or can provide them on request.
ALLOWED_IPS = ["203.0.113.10", "203.0.113.11", "198.51.100.0/24"]
function validate_ip(request_ip):
for allowed in ALLOWED_IPS:
if ip_matches(request_ip, allowed):
return true
return false
Secret Token Validation
Include a static secret in your postback URL that your handler validates on every request. Rotate this secret periodically (quarterly is a reasonable cadence).
Transaction Deduplication
The unique index on transaction_id in your conversions table prevents duplicate crediting at the database level. Your handler should also check for duplicates before attempting to insert.
Payout Validation
Cross-reference the payout in the postback against the expected payout for that offer in your database. Flag significant discrepancies for manual review:
function validate_payout(postback_payout, expected_payout):
if abs(postback_payout - expected_payout) > (expected_payout * 0.20):
// Payout differs by more than 20% from expected
flag_for_review("Payout mismatch: expected " + expected_payout + ", got " + postback_payout)
return false
return true
Rate Limiting
Apply rate limiting to your postback endpoint. Under normal conditions, postbacks arrive at a steady, predictable rate. A sudden spike could indicate an attack or misconfiguration.
Request Logging
Log every postback request, including the full URL, headers, source IP, and timestamp. This audit trail is invaluable for debugging, fraud investigation, and revenue reconciliation.
Step 6: Testing
Unit Testing
Write tests for each component of your integration:
- Offer API parsing (handle malformed responses, missing fields, empty feeds)
- Click URL generation (correct token substitution, URL encoding)
- Postback handler (valid request, duplicate request, invalid secret, missing fields, reversal)
- User crediting (correct amount, balance update, notification trigger)
Integration Testing
Test the full flow end-to-end:
- Fetch offers from the API and verify they display correctly
- Click an offer link and verify the redirect works (lands on the advertiser's page)
- Simulate a postback by calling your endpoint with test parameters
- Verify the conversion is recorded in your database
- Verify the user's balance is updated correctly
- Send a duplicate postback and verify it is rejected
- Send a postback with an invalid secret and verify it returns 403
- Send a reversal postback and verify the balance is deducted
Live Testing
Before going to production, ask your CPA network to send a test postback or complete a test conversion yourself. Verify the entire flow works with real network data.
Production Considerations
Reliability
- High availability — Your postback endpoint must be available 24/7. Use load balancing and redundancy for production deployments.
- Response time — Respond to postbacks within 1-2 seconds. Use asynchronous processing for heavy operations (notifications, analytics) — do the credit and acknowledgment synchronously.
- Error recovery — If your handler encounters an unexpected error, return a 500 status so the network retries. But handle known conditions (duplicates, missing users) with 200 to prevent infinite retries.
Monitoring
- Set up alerts for: zero postbacks in the last hour (possible issue), error rate above 1%, response time above 2 seconds
- Daily reconciliation: compare your conversion count against the network's dashboard
- Weekly review: check for payout discrepancies, unusual conversion patterns, and fraud signals
Scaling
- Database indexing is critical as your conversions table grows. Index on
transaction_id,user_id, andcreated_at - Use connection pooling for database connections
- For very high volume (1,000+ postbacks/minute), consider a message queue (RabbitMQ, SQS, Redis streams) between receiving and processing
Working with Multiple Networks
Most production offerwalls aggregate offers from 2-5 CPA networks. To support multiple networks:
- Normalize offer data — Map each network's offer format to a common internal format during the sync process
- Separate postback endpoints per network — Use
/api/postback/revboost,/api/postback/network_b, etc., or a single endpoint with anetworkparameter. This allows network-specific validation (different IP whitelists, different token names) - Unified offer display — Merge offers from all networks into a single sorted list on your offerwall. The user should not know (or care) which network provides each offer.
- Track network attribution — Record which network each conversion came from for revenue reporting and reconciliation
Getting Started with RevBoost's API
RevBoost provides an offer feed API and S2S postback support for publishers building custom offerwalls. Here is how to get started:
- Apply as a publisher — Submit your application and mention that you need API access for a custom offerwall integration.
- Get your credentials — Your account manager will provide API keys, documentation, and postback configuration instructions.
- Build and test — Follow the integration steps in this guide to build your offerwall.
- Go live — Deploy to production, configure your production postback URL, and start generating revenue.
- Optimize — Work with your RevBoost AM to identify the best-performing offers and optimize your offerwall for maximum revenue.
Building a custom offerwall via API integration is more work than embedding a third-party widget, but the payoff is significant: better user experience, higher revenue (no middleman fee), and complete control over your platform. For any serious rewards site or GPT platform, it is the right investment.
Ready to Integrate? Get API Access.
RevBoost provides offer feed APIs, S2S postback tracking, and dedicated technical support for publishers building custom offerwalls. Apply for access and start building.
Apply as a Publisher