SDK Documentation v1.0.0

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.

1

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.

2

Choose Integration Method

Select your platform from the options above. We support native Android, iOS, Unity, React Native, Flutter, and Web integrations.

3

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!

4

Configure Postback (S2S)

Set up Server-to-Server callbacks to receive reward notifications securely. See the S2S Callback section for details.

5

Test & Launch

Test your integration using our sandbox environment, then go live and start monetizing your app!

Server-to-Server (S2S) Callbacks

RECOMMENDED

Why 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

1

User Completes Offer

Inside your app
2

We Send POST

To your server
3

You Verify & Reward

Give user coins

S2S Endpoint

We'll send a POST request to your server when a user completes an offer:

POST Your Callback URL (configured in dashboard)
{
  "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

ParameterTypeRequiredDescription
user_idstringYesUnique identifier of the user
offer_idstringYesUnique identifier of the offer
offer_namestringYesName of the completed offer
rewardintegerYesAmount to reward the user
currencystringYesVirtual currency name
payoutfloatYesYour earnings in USD
statusstringYesOffer status (completed/pending)
timestampintegerYesUnix timestamp
signaturestringYesHMAC-SHA256 signature
transaction_idstringYesUnique transaction ID
device_typestringNoDevice type (android/ios)
countrystringNoUser's country code

Signature Verification

Always verify the signature to prevent fraud:

PHP
<?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']);
?>
Node.js
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);
Python
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)
Java
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

CodeStatusDescription
200OKReward processed successfully
400Bad RequestMissing or invalid parameters
401UnauthorizedInvalid signature
409ConflictDuplicate transaction
500Server ErrorInternal server error

Android Integration

Step 1: Add SDK to Your Project

Create a new file InfyClickSDK.kt in your project:

InfyClickSDK.kt
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

MainActivity.kt
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:

InfyClickSDK.java
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):

Gradle
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:

InfyClickSDK.swift
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

ViewController.swift
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

InfyClickSDK.h
#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:

Xcode
// 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:

InfyClickSDK.cs
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

GameManager.cs
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:

Gradle
dependencies {
    implementation 'androidx.browser:browser:1.7.0'
}

React Native Integration

Installation

bash
# 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:

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

App.js
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

bash
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:

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  url_launcher: ^6.2.1
  flutter_inappwebview: ^6.0.0

SDK Implementation

Create lib/infyclick_sdk.dart:

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

main.dart
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:

HTML
<!-- 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:

JavaScript
// 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

HTML + JS
<!-- 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:

bash
# 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

1

Go to Publisher Dashboard

Navigate to your app settings and find the Postback URL field under the Integration tab.

2

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).

3

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.

4

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:

MacroDescriptionExample
{user_id}The user ID passed in SDK inituser_12345
{offer_id}Unique offer identifieroffer_678
{offer_name}Name of the completed offerPremium Subscription
{reward}Reward amount in virtual currency100
{payout}Your earnings in USD0.50
{transaction_id}Unique transaction IDtxn_abc123
{signature}HMAC-SHA256 signaturea3f5d8...

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:

1

Concatenate Parameters

Join user_id + offer_id + reward + timestamp into a single string.

2

Generate HMAC

Compute HMAC-SHA256 of the concatenated string using your secret key.

3

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:

Pseudocode
// 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:

Pseudocode
// 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

CodeNameDescriptionSolution
ERR_001Invalid API KeyThe API key is missing or incorrectCheck your API key in the Publisher Dashboard
ERR_002Invalid User IDThe user ID format is not validEnsure user_id is a non-empty string
ERR_003Network ErrorCould not connect to the serverCheck internet connection and retry
ERR_004Offerwall UnavailableThe offerwall failed to loadTry again later or check API key
ERR_005Initialization FailedSDK was not initialized before useCall initialize() before other methods

S2S Callback Error Codes

HTTP CodeNameDescription
200SuccessCallback processed successfully
400Bad RequestMissing or invalid parameters in the request
401UnauthorizedSignature verification failed - check your secret key
409ConflictDuplicate transaction_id - already processed
422UnprocessableValid signature but invalid data (e.g., negative reward)
500Server ErrorInternal 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

EndpointMethodDescription
/api/offerwall/{api_key}GETDisplay offerwall iframe
/api/offerwall/balanceGETGet user balance
/api/s2s/callbackPOSTYour S2S endpoint (you implement)
/api/offerwall/clickPOSTTrack offer click (internal)

Balance Response Format

JSON
{
  "balance": 1500,
  "currency": "coins",
  "user_id": "user_12345",
  "pending_rewards": 200
}

Frequently Asked Questions

S2S (Server-to-Server) callback is a secure method where our server sends a POST request directly to your server when a user completes an offer.
Each callback includes a unique transaction_id. Store these IDs in your database and check for duplicates.
We retry callbacks up to 5 times with exponential backoff.
Yes! Use our sandbox API key to test. Sandbox callbacks are marked with "sandbox": true.
Most callbacks are sent within seconds. Some offers may take up to 24 hours.

Need Help?