Offerwall SDK
Monetize your app with the industry's highest-paying offerwall. Complete integration guides for Android, iOS, Unity, React Native, and Flutter.
Choose Your Platform
Quick Start
Get up and running with InfyClick SDK in just a few minutes. Follow these steps to integrate the offerwall into your app.
Get Your API Key
Login to your Publisher Dashboard and create a new app to get your unique API key. This key is required for all SDK integrations.
Choose Integration Method
Select your platform from the options above. We support native Android, iOS, Unity, React Native, Flutter, and Web integrations.
Copy SDK Code
Copy the SDK code examples from this documentation and paste them into your project. No SDK package download required - just copy and paste!
Configure Postback (S2S)
Set up Server-to-Server callbacks to receive reward notifications securely. See the S2S Callback section for details.
Test & Launch
Test your integration using our sandbox environment, then go live and start monetizing your app!
Server-to-Server (S2S) Callbacks
RECOMMENDEDWhy S2S is Better
Server-to-Server callbacks are more secure than client-side callbacks. They prevent fraud, tampering, and ensure reliable reward delivery even if the user closes the app.
How S2S Works
User Completes Offer
Inside your appWe Send POST
To your serverYou Verify & Reward
Give user coinsS2S Endpoint
We'll send a POST request to your server when a user completes an offer:
{
"user_id": "user_12345",
"offer_id": "offer_678",
"offer_name": "Premium Subscription",
"reward": 100,
"currency": "coins",
"payout": 0.50,
"status": "completed",
"timestamp": 1699123456,
"signature": "a3f5d8...",
"transaction_id": "txn_abc123",
"device_type": "android",
"country": "US"
}
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string | Yes | Unique identifier of the user |
offer_id | string | Yes | Unique identifier of the offer |
offer_name | string | Yes | Name of the completed offer |
reward | integer | Yes | Amount to reward the user |
currency | string | Yes | Virtual currency name |
payout | float | Yes | Your earnings in USD |
status | string | Yes | Offer status (completed/pending) |
timestamp | integer | Yes | Unix timestamp |
signature | string | Yes | HMAC-SHA256 signature |
transaction_id | string | Yes | Unique transaction ID |
device_type | string | No | Device type (android/ios) |
country | string | No | User's country code |
Signature Verification
Always verify the signature to prevent fraud:
<?php
// Get your secret key from Publisher Dashboard
$secret_key = 'your_secret_key_here';
// Get POST data
$user_id = $_POST['user_id'];
$offer_id = $_POST['offer_id'];
$reward = $_POST['reward'];
$timestamp = $_POST['timestamp'];
$signature = $_POST['signature'];
// Generate expected signature
$data = $user_id . $offer_id . $reward . $timestamp;
$expected_signature = hash_hmac('sha256', $data, $secret_key);
// Verify signature
if (!hash_equals($expected_signature, $signature)) {
http_response_code(401);
echo json_encode(['status' => 'invalid_signature']);
exit;
}
// Check for duplicate transactions
// ... your duplicate check logic here
// Credit the user
// ... your reward logic here
// Return success
echo json_encode(['status' => 'success']);
?>
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
const SECRET_KEY = 'your_secret_key_here';
app.post('/s2s/callback', (req, res) => {
const { user_id, offer_id, reward, timestamp, signature } = req.body;
// Generate expected signature
const data = user_id + offer_id + reward + timestamp;
const expectedSignature = crypto
.createHmac('sha256', SECRET_KEY)
.update(data)
.digest('hex');
// Verify signature
if (signature !== expectedSignature) {
return res.status(401).json({ status: 'invalid_signature' });
}
// Check timestamp (prevent replay attacks)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return res.status(400).json({ status: 'expired' });
}
// Check for duplicate
// ... your duplicate check logic
// Credit the user
// ... your reward logic
res.json({ status: 'success' });
});
app.listen(3000);
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = b'your_secret_key_here'
@app.route('/s2s/callback', methods=['POST'])
def s2s_callback():
data = request.form
user_id = data.get('user_id')
offer_id = data.get('offer_id')
reward = data.get('reward')
timestamp = data.get('timestamp')
signature = data.get('signature')
# Generate expected signature
message = f"{user_id}{offer_id}{reward}{timestamp}".encode()
expected = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()
# Verify signature
if not hmac.compare_digest(expected, signature):
return jsonify({'status': 'invalid_signature'}), 401
# Credit the user
# ... your reward logic
return jsonify({'status': 'success'})
if __name__ == '__main__':
app.run(port=3000)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
@PostMapping("/s2s/callback")
public ResponseEntity<?> s2sCallback(
@RequestParam String user_id,
@RequestParam String offer_id,
@RequestParam String reward,
@RequestParam String timestamp,
@RequestParam String signature) throws Exception {
String secretKey = "your_secret_key_here";
String data = user_id + offer_id + reward + timestamp;
// Generate HMAC
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
mac.init(secret);
byte[] hash = mac.doFinal(data.getBytes());
// Convert to hex
StringBuilder expected = new StringBuilder();
for (byte b : hash) {
expected.append(String.format("%02x", b));
}
// Verify signature
if (!expected.toString().equals(signature)) {
return ResponseEntity.status(401)
.body(Map.of("status", "invalid_signature"));
}
// Credit the user
// ... your reward logic
return ResponseEntity.ok(Map.of("status", "success"));
}
Response Codes
| Code | Status | Description |
|---|---|---|
200 | OK | Reward processed successfully |
400 | Bad Request | Missing or invalid parameters |
401 | Unauthorized | Invalid signature |
409 | Conflict | Duplicate transaction |
500 | Server Error | Internal server error |
Android Integration
Step 1: Add SDK to Your Project
Create a new file InfyClickSDK.kt in your project:
package
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.browser.customtabs.CustomTabsIntent
class InfyClick private constructor(private val context: Context) {
private var apiKey: String = ""
private var userId: String = ""
private var deviceId: String = ""
private var sandbox: Boolean = false
companion object {
private const val BASE_URL = "https://infyclick.com"
private const val TAG = "InfyClick"
@Volatile
private var instance: InfyClick? = null
fun getInstance(context: Context): InfyClick {
return instance ?: synchronized(this) {
instance ?: InfyClick(context.applicationContext).also { instance = it }
}
}
}
fun initialize(apiKey: String, userId: String, deviceId: String = "", sandbox: Boolean = false): InfyClick {
this.apiKey = apiKey
this.userId = userId
this.deviceId = deviceId
this.sandbox = sandbox
Log.d(TAG, "SDK initialized for user: $userId")
return this
}
fun showOfferwall(activity: Activity) {
val url = buildOfferwallUrl()
// Option 1: Use Chrome Custom Tabs (Recommended)
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
customTabsIntent.launchUrl(activity, Uri.parse(url))
// Option 2: Use WebView (see full implementation in docs)
}
private fun buildOfferwallUrl(): String {
val sandboxParam = if (sandbox) "&sandbox=1" else ""
return "$BASE_URL/api/offerwall/$apiKey?user_id=$userId&device_id=$deviceId&platform=android$sandboxParam"
}
fun getBalance(callback: (Int) -> Unit) {
// Fetch user balance from server
val url = "$BASE_URL/api/balance?api_key=$apiKey&user_id=$userId"
// Implementation using OkHttp or similar
}
fun trackEvent(eventName: String, params: Map = emptyMap()) {
// Track custom events
val url = "$BASE_URL/api/events"
// Implementation using OkHttp or similar
}
}
Step 2: Use in Your Activity
class MainActivity : AppCompatActivity() {
private val YOUR_API_KEY = "your_api_key_here"
private val YOUR_USER_ID = "user_${System.currentTimeMillis()}"
private val YOUR_DEVICE_ID = Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize SDK
val sdk = InfyClick.getInstance(this)
.initialize(YOUR_API_KEY, YOUR_USER_ID, YOUR_DEVICE_ID, sandbox = false)
// Set reward listener
sdk.setRewardListener { reward ->
runOnUiThread {
Toast.makeText(
this,
"+${reward.amount} ${reward.currency} earned!",
Toast.LENGTH_SHORT
).show()
}
}
// Show offerwall button
findViewById<Button>(R.id.btnOfferwall).setOnClickListener {
sdk.showOfferwall(this)
}
// Get user balance
sdk.getBalance { balance ->
runOnUiThread {
findViewById<TextView>(R.id.tvBalance).text = "Coins: $balance"
}
}
}
}
Step 1: Add SDK to Your Project
Create a new file InfyClickSDK.java in your project:
package ;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.provider.Settings;
import android.util.Log;
import androidx.browser.customtabs.CustomTabsIntent;
public class InfyClick {
private static final String BASE_URL = "https://infyclick.com";
private static final String TAG = "InfyClick";
private static InfyClick instance;
private Context context;
private String apiKey;
private String userId;
private String deviceId;
private boolean sandbox;
private InfyClick(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized InfyClick getInstance(Context context) {
if (instance == null) {
instance = new InfyClick(context);
}
return instance;
}
public InfyClick initialize(String apiKey, String userId, String deviceId, boolean sandbox) {
this.apiKey = apiKey;
this.userId = userId;
this.deviceId = deviceId;
this.sandbox = sandbox;
Log.d(TAG, "SDK initialized for user: " + userId);
return this;
}
public InfyClick initialize(String apiKey, String userId) {
return initialize(apiKey, userId, "", false);
}
public void showOfferwall(Activity activity) {
String url = buildOfferwallUrl();
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder()
.setShowTitle(true)
.build();
customTabsIntent.launchUrl(activity, Uri.parse(url));
}
private String buildOfferwallUrl() {
String sandboxParam = sandbox ? "&sandbox=1" : "";
return BASE_URL + "/api/offerwall/" + apiKey + "?user_id=" + userId + "&device_id=" + deviceId + "&platform=android" + sandboxParam;
}
public void getBalance(BalanceCallback callback) {
// Fetch user balance from server
String url = BASE_URL + "/api/balance?api_key=" + apiKey + "&user_id=" + userId;
// Implementation using OkHttp or similar
}
public void trackEvent(String eventName, java.util.Map params) {
// Track custom events
String url = BASE_URL + "/api/events";
// Implementation using OkHttp or similar
}
public interface BalanceCallback {
void onBalanceReceived(int balance);
void onError(String error);
}
}
Required Dependencies
Add to your build.gradle (Module: app):
dependencies {
// Chrome Custom Tabs (for offerwall display)
implementation 'androidx.browser:browser:1.7.0'
// HTTP client (for API calls)
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
// JSON parsing
implementation 'com.google.code.gson:gson:2.10.1'
}
iOS Integration
Step 1: Create SDK File
Create InfyClickSDK.swift in your project:
import UIKit
import SafariServices
class InfyClick {
static let shared = InfyClick()
private let baseURL = "https://infyclick.com"
private var apiKey: String = ""
private var userId: String = ""
private var deviceId: String = ""
private var sandbox: Bool = false
private init() {}
func initialize(apiKey: String, userId: String, deviceId: String = "", sandbox: Bool = false) {
self.apiKey = apiKey
self.userId = userId
self.deviceId = deviceId
self.sandbox = sandbox
print("InfyClick initialized for user: \(userId)")
}
func showOfferwall(from viewController: UIViewController) {
let sandboxParam = sandbox ? "&sandbox=1" : ""
let urlString = "\(baseURL)/api/offerwall/\(apiKey)?user_id=\(userId)&device_id=\(deviceId)&platform=ios\(sandboxParam)"
guard let url = URL(string: urlString) else { return }
// Use SFSafariViewController for best user experience
let safariVC = SFSafariViewController(url: url)
safariVC.modalPresentationStyle = .fullScreen
viewController.present(safariVC, animated: true)
}
func getBalance(completion: @escaping (Int) -> Void) {
let urlString = "\(baseURL)/api/balance?api_key=\(apiKey)&user_id=\(userId)"
guard let url = URL(string: urlString) else {
completion(0)
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let balance = json["balance"] as? Int else {
DispatchQueue.main.async { completion(0) }
return
}
DispatchQueue.main.async { completion(balance) }
}.resume()
}
func trackEvent(eventName: String, params: [String: Any] = [:]) {
// Track custom events
let urlString = "\(baseURL)/api/events"
// Implementation using URLSession
}
}
Step 2: Use in Your ViewController
import UIKit
class ViewController: UIViewController {
let apiKey = "your_api_key_here"
let userId = "user_\(UUID().uuidString)"
let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? ""
override func viewDidLoad() {
super.viewDidLoad()
// Initialize SDK
InfyClick.shared.initialize(apiKey: apiKey, userId: userId, deviceId: deviceId, sandbox: false)
// Set reward listener
InfyClick.shared.setRewardListener { reward in
DispatchQueue.main.async {
let alert = UIAlertController(
title: "Reward Earned!",
message: "+\(reward.amount) \(reward.currency)",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
}
// Create offerwall button
let button = UIButton(type: .system)
button.setTitle("Open Offerwall", for: .normal)
button.addTarget(self, action: #selector(showOfferwall), for: .touchUpInside)
button.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
view.addSubview(button)
}
@objc func showOfferwall() {
InfyClick.shared.showOfferwall(from: self)
}
}
Objective-C Implementation
#import <UIKit/UIKit.h>
@interface InfyClick : NSObject
+ (instancetype)sharedInstance;
- (void)initializeWithApiKey:(NSString *)apiKey userId:(NSString *)userId deviceId:(NSString *)deviceId sandbox:(BOOL)sandbox;
- (void)showOfferwallFromViewController:(UIViewController *)viewController;
- (void)getBalanceWithCompletion:(void (^)(NSInteger balance))completion;
- (void)trackEvent:(NSString *)eventName params:(NSDictionary *)params;
@end
Required Frameworks
Add to your project:
// Add these frameworks in Xcode:
// - SafariServices.framework (for SFSafariViewController)
// In your Project Settings > General > Frameworks, Libraries:
// SafariServices.framework
Unity Integration
Step 1: Create SDK Script
Create InfyClickSDK.cs in Assets/Scripts:
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class InfyClickSDK : MonoBehaviour
{
private static InfyClickSDK _instance;
public static InfyClickSDK Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("InfyClickSDK");
_instance = go.AddComponent<InfyClickSDK>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
private string baseURL = "https://infyclick.com";
private string apiKey;
private string userId;
private string deviceId;
private bool sandbox;
public void Initialize(string apiKey, string userId, string deviceId = "", bool sandbox = false)
{
this.apiKey = apiKey;
this.userId = userId;
this.deviceId = deviceId;
this.sandbox = sandbox;
Debug.Log($"InfyClickSDK initialized for user: {userId}");
}
public void ShowOfferwall()
{
string sandboxParam = sandbox ? "&sandbox=1" : "";
string url = $"{baseURL}/api/offerwall/{apiKey}?user_id={userId}&device_id={deviceId}&platform=unity{sandboxParam}";
#if UNITY_ANDROID
// Use Chrome Custom Tabs on Android
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
using (AndroidJavaClass customTabs = new AndroidJavaClass("androidx.browser.customtabs.CustomTabsIntent"))
using (AndroidJavaObject builder = new AndroidJavaObject("androidx.browser.customtabs.CustomTabsIntent$Builder"))
using (AndroidJavaObject customTabsIntent = builder.Call<AndroidJavaObject>("build"))
{
customTabsIntent.Call("launchUrl", currentActivity, new AndroidJavaObject("android.net.Uri", url));
}
#elif UNITY_IOS
// Use Safari on iOS
Application.OpenURL(url);
#else
Application.OpenURL(url);
#endif
}
public void GetBalance(System.Action<int> callback)
{
StartCoroutine(GetBalanceCoroutine(callback));
}
private IEnumerator GetBalanceCoroutine(System.Action<int> callback)
{
string url = $"{baseURL}/api/balance?api_key={apiKey}&user_id={userId}";
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
string json = request.downloadHandler.text;
BalanceResponse response = JsonUtility.FromJson<BalanceResponse>(json);
callback?.Invoke(response.balance);
}
else
{
callback?.Invoke(0);
}
}
}
public void TrackEvent(string eventName, System.Collections.Generic.Dictionary<string, object> parameters = null)
{
StartCoroutine(TrackEventCoroutine(eventName, parameters));
}
private IEnumerator TrackEventCoroutine(string eventName, System.Collections.Generic.Dictionary<string, object> parameters)
{
string url = $"{baseURL}/api/events";
// Implementation using UnityWebRequest
yield return null;
}
}
[System.Serializable]
private class BalanceResponse
{
public int balance;
public string currency;
}
}
Step 2: Use in Your Game
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public string apiKey = "your_api_key_here";
public string userId = "user_" + System.Guid.NewGuid().ToString();
public string deviceId = SystemInfo.deviceUniqueIdentifier;
public Text balanceText;
public Button offerwallButton;
void Start()
{
// Initialize SDK
InfyClickSDK.Instance.Initialize(apiKey, userId, deviceId, sandbox: false);
// Setup button
offerwallButton.onClick.AddListener(OpenOfferwall);
// Get initial balance
UpdateBalance();
}
void OpenOfferwall()
{
InfyClickSDK.Instance.ShowOfferwall();
}
void UpdateBalance()
{
InfyClickSDK.Instance.GetBalance((balance) => {
balanceText.text = $"Coins: {balance}";
});
}
}
Android Dependencies
For Android builds, add to Assets/Plugins/Android/mainTemplate.gradle:
dependencies {
implementation 'androidx.browser:browser:1.7.0'
}
React Native Integration
Installation
# No npm package needed! Just copy the SDK file below
# Required dependencies (usually already installed):
npm install @react-native-community/netinfo
SDK Implementation
Create InfyClickSDK.js:
import { Linking, Platform } from 'react-native';
import InAppBrowser from 'react-native-inappbrowser-reborn';
const BASE_URL = 'https://infyclick.com';
class InfyClickSDK {
constructor() {
this.apiKey = '';
this.userId = '';
this.deviceId = '';
this.sandbox = false;
}
initialize(apiKey, userId, deviceId = '', sandbox = false) {
this.apiKey = apiKey;
this.userId = userId;
this.deviceId = deviceId;
this.sandbox = sandbox;
console.log('InfyClickSDK initialized for user:', userId);
return this;
}
async showOfferwall() {
const sandboxParam = this.sandbox ? '&sandbox=1' : '';
const url = `${BASE_URL}/api/offerwall/${this.apiKey}?user_id=${this.userId}&device_id=${this.deviceId}&platform=${Platform.OS}${sandboxParam}`;
try {
if (await InAppBrowser.isAvailable()) {
await InAppBrowser.open(url, {
dismissButtonStyle: 'cancel',
preferredBarTintColor: '#0c4a6e',
preferredControlTintColor: 'white',
readerMode: false,
animated: true,
modalPresentationStyle: 'fullScreen',
modalTransitionStyle: 'coverVertical',
modalEnabled: true,
enableBarCollapsing: false,
});
} else {
Linking.openURL(url);
}
} catch (error) {
console.error('Error opening offerwall:', error);
Linking.openURL(url);
}
}
async getBalance() {
try {
const url = `${BASE_URL}/api/balance?api_key=${this.apiKey}&user_id=${this.userId}`;
const response = await fetch(url);
const data = await response.json();
return data.balance || 0;
} catch (error) {
console.error('Error fetching balance:', error);
return 0;
}
}
async trackEvent(eventName, params = {}) {
try {
const url = `${BASE_URL}/api/events`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: this.apiKey,
user_id: this.userId,
event_name: eventName,
params
})
});
return await response.json();
} catch (error) {
console.error('Error tracking event:', error);
}
}
}
export default new InfyClickSDK();
Usage in Your App
import React, { useEffect, useState } from 'react';
import { View, Text, Button, StyleSheet, Platform } from 'react-native';
import InfyClickSDK from './InfyClickSDK';
const API_KEY = 'your_api_key_here';
const USER_ID = 'user_' + Date.now();
const DEVICE_ID = Platform.OS === 'ios' ?
'ios_device_id_here' :
'android_device_id_here';
export default function App() {
const [balance, setBalance] = useState(0);
useEffect(() => {
// Initialize SDK
InfyClickSDK.initialize(API_KEY, USER_ID, DEVICE_ID, false);
// Get initial balance
updateBalance();
}, []);
const updateBalance = async () => {
const newBalance = await InfyClickSDK.getBalance();
setBalance(newBalance);
};
const openOfferwall = async () => {
await InfyClickSDK.showOfferwall();
// Refresh balance when returning
updateBalance();
};
return (
<View style={styles.container}>
<Text style={styles.balance}>Coins: {balance}</Text>
<Button title="Earn Coins" onPress={openOfferwall} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
balance: {
fontSize: 24,
marginBottom: 20,
fontWeight: 'bold',
},
});
Install In-App Browser
npm install react-native-inappbrowser-reborn
# or
yarn add react-native-inappbrowser-reborn
# iOS - install pods
cd ios && pod install
Flutter Integration
Installation
Add dependencies to pubspec.yaml:
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.2.1
flutter_inappwebview: ^6.0.0
SDK Implementation
Create lib/infyclick_sdk.dart:
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
class InfyClickSDK {
static final InfyClickSDK _instance = InfyClickSDK._internal();
factory InfyClickSDK() => _instance;
InfyClickSDK._internal();
static const String baseURL = 'https://infyclick.com';
String _apiKey = '';
String _userId = '';
String _deviceId = '';
bool _sandbox = false;
Future<void> initialize(String apiKey, String userId, {String deviceId = '', bool sandbox = false}) async {
_apiKey = apiKey;
_userId = userId;
_deviceId = deviceId;
_sandbox = sandbox;
if (_deviceId.isEmpty) {
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
_deviceId = androidInfo.id;
} else if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo;
_deviceId = iosInfo.identifierForVendor ?? '';
}
}
print('InfyClickSDK initialized for user: $userId');
}
Future<void> showOfferwall(BuildContext context) async {
final sandboxParam = _sandbox ? '&sandbox=1' : '';
final url = '$baseURL/api/offerwall/$_apiKey?user_id=$_userId&device_id=$_deviceId&platform=${Platform.operatingSystem.toLowerCase()}$sandboxParam';
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OfferwallScreen(url: url),
),
);
}
Future<int> getBalance() async {
try {
final url = '$baseURL/api/balance?api_key=$_apiKey&user_id=$_userId';
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['balance'] ?? 0;
}
return 0;
} catch (e) {
print('Error fetching balance: $e');
return 0;
}
}
Future<Map<String, dynamic>> trackEvent(String eventName, {Map<String, dynamic>? params}) async {
try {
final url = Uri.parse('$baseURL/api/events');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'api_key': _apiKey,
'user_id': _userId,
'event_name': eventName,
'params': params ?? {},
}),
);
if (response.statusCode == 200) {
return jsonDecode(response.body);
}
return {'error': 'Failed to track event'};
} catch (e) {
print('Error tracking event: $e');
return {'error': e.toString()};
}
}
}
class OfferwallScreen extends StatelessWidget {
final String url;
const OfferwallScreen({Key? key, required this.url}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Earn Rewards'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
),
body: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse(url)),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
),
),
),
);
}
}
Usage in Your App
import 'package:flutter/material.dart';
import 'infyclick_sdk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'InfyClick Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final String apiKey = 'your_api_key_here';
final String userId = 'user_${DateTime.now().millisecondsSinceEpoch}';
int balance = 0;
@override
void initState() {
super.initState();
InfyClickSDK().initialize(apiKey, userId, sandbox: false);
_updateBalance();
}
Future<void> _updateBalance() async {
final newBalance = await InfyClickSDK().getBalance();
setState(() => balance = newBalance);
}
Future<void> _openOfferwall() async {
await InfyClickSDK().showOfferwall(context);
_updateBalance();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('InfyClick Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Coins: $balance',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: _openOfferwall,
icon: const Icon(Icons.card_giftcard),
label: const Text('Earn More Coins'),
),
],
),
),
);
}
}
Web / iFrame Integration
Method 1: Direct iFrame Embed
Add this HTML to your website:
<!-- Offerwall Container -->
<div id="offerwall-container" style="width: 100%; height: 600px; display: none;">
<iframe
id="offerwall-iframe"
src="https://infyclick.com/api/offerwall/YOUR_API_KEY?user_id=USER_ID&platform=web"
style="width: 100%; height: 100%; border: none;"
allow="fullscreen"
></iframe>
</div>
<button onclick="showOfferwall()">Earn Rewards</button>
<script>
const API_KEY = 'your_api_key_here';
const USER_ID = 'user_' + Date.now();
const BASE_URL = 'https://infyclick.com';
function showOfferwall() {
const container = document.getElementById('offerwall-container');
const iframe = document.getElementById('offerwall-iframe');
// Update iframe src with dynamic values
iframe.src = `${BASE_URL}/api/offerwall/${API_KEY}?user_id=${USER_ID}&platform=web`;
container.style.display = 'block';
}
function hideOfferwall() {
document.getElementById('offerwall-container').style.display = 'none';
// Refresh balance when closing
updateBalance();
}
async function updateBalance() {
const response = await fetch(
`${BASE_URL}/api/balance?api_key=${API_KEY}&user_id=${USER_ID}`
);
const data = await response.json();
document.getElementById('balance').textContent = `Coins: ${data.balance}`;
}
</script>
Method 2: JavaScript SDK
Include our lightweight JavaScript SDK:
// infyclick-sdk.js
class InfyClickSDK {
constructor() {
this.baseURL = 'https://infyclick.com';
this.apiKey = '';
this.userId = '';
this.modal = null;
}
init(apiKey, userId, deviceId = '', sandbox = false) {
this.apiKey = apiKey;
this.userId = userId;
this.deviceId = deviceId;
this.sandbox = sandbox;
console.log('InfyClickSDK initialized');
return this;
}
showOfferwall() {
const sandboxParam = this.sandbox ? '&sandbox=1' : '';
const url = `${this.baseURL}/api/offerwall/${this.apiKey}?user_id=${this.userId}&device_id=${this.deviceId}&platform=web${sandboxParam}`;
// Create modal
this.modal = document.createElement('div');
this.modal.innerHTML = `
<div style="
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
">
<div style="
width: 90%;
height: 90%;
background: white;
border-radius: 8px;
overflow: hidden;
position: relative;
">
<button onclick="this.closest('.infyclick-modal').remove()"
style="
position: absolute;
top: 10px; right: 10px;
z-index: 10;
padding: 8px 16px;
background: #333;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
">
Close
</button>
<iframe
src="${url}"
style="width: 100%; height: 100%; border: none;"
allow="fullscreen"
></iframe>
</div>
</div>
`;
this.modal.className = 'infyclick-modal';
document.body.appendChild(this.modal);
}
async getBalance() {
const response = await fetch(
`${this.baseURL}/api/balance?api_key=${this.apiKey}&user_id=${this.userId}`
);
const data = await response.json();
return data.balance;
}
async trackEvent(eventName, params = {}) {
const response = await fetch(`${this.baseURL}/api/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: this.apiKey,
user_id: this.userId,
event_name: eventName,
params
})
});
return await response.json();
}
}
// Auto-initialize if config exists
window.InfyClick = new InfyClickSDK();
Usage Example
<!-- Include SDK -->
<script src="infyclick-sdk.js"></script>
<!-- Your HTML -->
<div>
<span id="balance">Coins: 0</span>
<button onclick="openOfferwall()">Earn More</button>
</div>
<script>
// Initialize
const API_KEY = 'your_api_key_here';
const USER_ID = 'user_' + Date.now();
InfyClick.init(API_KEY, USER_ID);
function openOfferwall() {
InfyClick.showOfferwall();
}
// Update balance periodically
async function updateBalance() {
const balance = await InfyClick.getBalance();
document.getElementById('balance').textContent = `Coins: ${balance}`;
}
setInterval(updateBalance, 30000); // Update every 30 seconds
updateBalance(); // Initial load
</script>
Testing Guide
Sandbox Mode Available
Use your sandbox API key for testing. All sandbox callbacks include "sandbox": true in the payload.
Testing Checklist
Test S2S Callback
Use this curl command to test your S2S endpoint:
# Test S2S callback endpoint
curl -X POST https://your-server.com/s2s/callback \
-d "user_id=test_user_123" \
-d "offer_id=offer_456" \
-d "reward=100" \
-d "currency=coins" \
-d "timestamp=$(date +%s)" \
-d "transaction_id=test_$(date +%s)" \
-d "signature=YOUR_GENERATED_SIGNATURE" \
-d "sandbox=true"
Postback Setup
Configure your postback URL to receive real-time notifications when users complete offers. Postbacks are sent as HTTP POST requests to your server.
Setting Up Your Postback URL
Go to Publisher Dashboard
Navigate to your app settings and find the Postback URL field under the Integration tab.
Enter Your Endpoint URL
Enter the full URL of your server endpoint that will receive the postback (e.g., https://your-server.com/api/s2s/callback).
Set Your Secret Key
Generate a secret key in the dashboard. This key is used to sign postback requests so you can verify their authenticity.
Test the Integration
Use the Test Postback button in the dashboard to send a test request and verify your server responds correctly.
Important
Your postback endpoint must return HTTP 200 to confirm receipt. Any other response will trigger our retry mechanism (up to 5 retries with exponential backoff).
Postback Macros
You can use these macros in your postback URL for dynamic values:
| Macro | Description | Example |
|---|---|---|
{user_id} | The user ID passed in SDK init | user_12345 |
{offer_id} | Unique offer identifier | offer_678 |
{offer_name} | Name of the completed offer | Premium Subscription |
{reward} | Reward amount in virtual currency | 100 |
{payout} | Your earnings in USD | 0.50 |
{transaction_id} | Unique transaction ID | txn_abc123 |
{signature} | HMAC-SHA256 signature | a3f5d8... |
Security
Protect your integration from fraud and ensure reward integrity with these security best practices.
Signature Verification
Always Verify Signatures
Never skip signature verification. Without it, attackers can forge postback requests and credit themselves rewards they did not earn.
Every postback includes an HMAC-SHA256 signature. To verify:
Concatenate Parameters
Join user_id + offer_id + reward + timestamp into a single string.
Generate HMAC
Compute HMAC-SHA256 of the concatenated string using your secret key.
Compare Signatures
Use a constant-time comparison function (e.g., hash_equals in PHP) to compare the expected signature with the received one.
Replay Attack Prevention
To prevent replay attacks, always check the timestamp parameter:
// Reject callbacks older than 5 minutes
const MAX_DELAY = 300; // seconds
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > MAX_DELAY) {
return 400; // Reject stale callback
}Duplicate Transaction Handling
Store every transaction_id in your database. Before crediting a user, check if the transaction was already processed:
// Check for duplicate
if (database.exists("transactions", transaction_id)) {
return 409; // Already processed
}
// Process reward
creditUser(user_id, reward);
// Mark as processed
database.insert("transactions", transaction_id);
return 200;Security Checklist
Error Codes
Reference for error codes returned by the InfyClick SDK and API endpoints.
SDK Error Codes
| Code | Name | Description | Solution |
|---|---|---|---|
ERR_001 | Invalid API Key | The API key is missing or incorrect | Check your API key in the Publisher Dashboard |
ERR_002 | Invalid User ID | The user ID format is not valid | Ensure user_id is a non-empty string |
ERR_003 | Network Error | Could not connect to the server | Check internet connection and retry |
ERR_004 | Offerwall Unavailable | The offerwall failed to load | Try again later or check API key |
ERR_005 | Initialization Failed | SDK was not initialized before use | Call initialize() before other methods |
S2S Callback Error Codes
| HTTP Code | Name | Description |
|---|---|---|
200 | Success | Callback processed successfully |
400 | Bad Request | Missing or invalid parameters in the request |
401 | Unauthorized | Signature verification failed - check your secret key |
409 | Conflict | Duplicate transaction_id - already processed |
422 | Unprocessable | Valid signature but invalid data (e.g., negative reward) |
500 | Server Error | Internal server error - will trigger retry |
Retry Behavior
Automatic Retries
If your server returns a 5xx error or does not respond, we will retry the callback up to 5 times with exponential backoff: 1 min, 5 min, 15 min, 1 hour, 6 hours.
API Reference
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/offerwall/{api_key} | GET | Display offerwall iframe |
/api/offerwall/balance | GET | Get user balance |
/api/s2s/callback | POST | Your S2S endpoint (you implement) |
/api/offerwall/click | POST | Track offer click (internal) |
Balance Response Format
{
"balance": 1500,
"currency": "coins",
"user_id": "user_12345",
"pending_rewards": 200
}