Skip to content

OfferWall SDK Advanced Usage ​

This documentation describes advanced features of the OfferWall SDK including server-side reward validation, event system, and best practices.

Server-Side Hash Validation ​

For projects with stricter security requirements, server-side hash validation can be enabled. In this mode, your backend must verify the incoming hash and generate a confirmation hash using your project's secret key.

Note: By default, hash validation is disabled and the simplified flow from the Quick Integration Guide applies — sdk.confirmReward(data.rewardId, data.hash) works directly without server-side hash generation.

1. Reward Claim Handler with Server Validation ​

js
sdk.on('rewardClaim', async (data) => {
  console.log('Reward claim received:', data);

  // Send to your backend for verification and get confirmation hash
  const confirmationHash = await verifyWithYourBackend(data);

  // Confirm the reward with server-generated hash
  if (confirmationHash) {
    sdk.confirmReward(data.rewardId, confirmationHash);
  }
});
ts
interface RewardClaim {
  rewardId: string | number;
  userId: string;
  projectId: string | number;
  hash: string;
  amount: number;
  description?: string;
}

sdk.on('rewardClaim', async (data: RewardClaim) => {
  console.log('Reward claim received:', data);

  const confirmationHash = await verifyWithYourBackend(data);

  if (confirmationHash) {
    sdk.confirmReward(data.rewardId, confirmationHash);
  }
});

2. Backend Verification Endpoint ​

Create an endpoint on your backend to verify the reward hash and generate a confirmation hash:

js
// Example backend endpoint (Node.js/Express)
const crypto = require('crypto');

app.post('/verify-reward', (req, res) => {
  const { rewardId, userId, projectId, amount, hash } = req.body;
  const secretKey = process.env.REWARD_SECRET_KEY;

  // 1. Verify the incoming hash
  const expectedHash = crypto
    .createHash('sha1')
    .update(`${userId}:${projectId}:${rewardId}:${amount}:${secretKey}`)
    .digest('hex');

  if (hash !== expectedHash) {
    return res.status(403).json({
      success: false,
      error: 'Invalid hash'
    });
  }

  // 2. Perform additional eligibility checks
  // - Check if user exists in your system
  // - Check if reward hasn't already been claimed
  // - Apply any business rules

  // 3. Generate confirmation hash
  const confirmationHash = crypto
    .createHash('sha1')
    .update(`${userId}:${projectId}:${rewardId}:${amount}:confirm:${secretKey}`)
    .digest('hex');

  // 4. Update user balance in your system

  // 5. Return confirmation hash
  res.json({
    success: true,
    confirmationHash
  });
});
ts
import { Request, Response } from 'express';
import crypto from 'crypto';

interface RewardVerificationRequest {
  rewardId: string | number;
  userId: string;
  projectId: string | number;
  amount: number;
  hash: string;
}

app.post('/verify-reward', (req: Request, res: Response) => {
  const { rewardId, userId, projectId, amount, hash } = req.body as RewardVerificationRequest;
  const secretKey = process.env.REWARD_SECRET_KEY as string;

  // 1. Verify the incoming hash
  const expectedHash = crypto
    .createHash('sha1')
    .update(`${userId}:${projectId}:${rewardId}:${amount}:${secretKey}`)
    .digest('hex');

  if (hash !== expectedHash) {
    return res.status(403).json({
      success: false,
      error: 'Invalid hash'
    });
  }

  // 2. Generate confirmation hash
  const confirmationHash = crypto
    .createHash('sha1')
    .update(`${userId}:${projectId}:${rewardId}:${amount}:confirm:${secretKey}`)
    .digest('hex');

  // 3. Update user balance in your system

  // 4. Return confirmation hash
  res.json({
    success: true,
    confirmationHash
  });
});

3. Client-Side Verification Function ​

js
async function verifyWithYourBackend(reward) {
  try {
    const response = await fetch('https://your-api.example.com/verify-reward', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(reward)
    });

    const result = await response.json();
    return result.success ? result.confirmationHash : null;
  } catch (error) {
    console.error('Error verifying reward:', error);
    return null;
  }
}
ts
interface VerificationResponse {
  success: boolean;
  confirmationHash?: string;
  error?: string;
}

async function verifyWithYourBackend(reward: RewardClaim): Promise<string | null> {
  try {
    const response = await fetch('https://your-api.example.com/verify-reward', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(reward)
    });

    const result: VerificationResponse = await response.json();
    return (result.success && result.confirmationHash) ? result.confirmationHash : null;
  } catch (error) {
    console.error('Error verifying reward:', error);
    return null;
  }
}

Recommendations for Server-side Validation ​

  1. Idempotence — your server should correctly handle repeated attempts to confirm the same reward:
javascript
function validateReward(rewardId, hash, userId) {
  if (!isValidHash(rewardId, hash, userId)) {
    return { success: false, error: 'Invalid hash' };
  }

  // Check if the reward has already been claimed
  if (isRewardAlreadyClaimed(rewardId, userId)) {
    return {
      success: true,
      confirmationHash: getStoredConfirmationHash(rewardId),
      alreadyClaimed: true
    };
  }

  grantRewardToUser(userId, rewardId);
  const confirmationHash = generateConfirmationHash(rewardId);
  storeConfirmationHash(rewardId, confirmationHash);

  return {
    success: true,
    confirmationHash
  };
}
  1. Status Tracking — always store information about reward status to avoid duplication

Checking for Available Offers ​

The hasOffers() function allows you to determine if any offers are available to the user before displaying the OfferWall button.

javascript
const sdk = await loadOfferWallSDK({
  projectId: 'YOUR_PROJECT_ID'
});

if (sdk.hasOffers()) {
  document.getElementById('offerwall-button').style.display = 'block';
} else {
  document.getElementById('offerwall-button').style.display = 'none';
  document.getElementById('no-offers-message').style.display = 'block';
}

This allows you to:

  • Improve user experience by not showing an empty OfferWall
  • Optimize the interface by hiding irrelevant elements
  • Offer alternative content when offers are unavailable

Processing Pending Rewards ​

The pending() method allows you to retrieve and process rewards that are waiting for confirmation. This is particularly useful for recovering from interrupted sessions.

javascript
async function checkPendingRewards() {
  const pendingRewards = await sdk.pending();
  console.log(`Found ${pendingRewards.length} pending rewards`);

  for (const reward of pendingRewards) {
    await processReward(reward);
  }
}

async function processReward(reward) {
  // Credit the user and confirm
  await creditUserReward(reward.userId, reward.amount);
  await sdk.confirmReward(reward.rewardId, reward.hash);
}

Reward Structure ​

javascript
{
  rewardId: "123",
  userId: "user_456",
  projectId: "YOUR_PROJECT_ID",
  hash: "verification_hash",
  amount: 500,
  description: "For completing the task"
}

SDK API Methods ​

Get Available Offers ​

Use getOffers() to fetch available and active tasks. This allows you to display offers in your own UI before opening the OfferWall.

js
const { available, active } = await sdk.getOffers();

// available — offers the user hasn't started yet
// active — offers the user is currently working on

// Each offer:
// {
//   id: "89864438b3",
//   title: "Install App X",
//   description: "Install and open the app",
//   icon: "https://...",
//   rewardAmount: 500
// }
ts
interface OfferItem {
  id: string | number;
  title: string;
  description?: string;
  icon?: string;
  rewardAmount: number;
}

const { available, active }: { available: OfferItem[]; active: OfferItem[] } = await sdk.getOffers();

Open a Specific Offer ​

Use openOffer(offerId) to open the OfferWall directly on a specific offer's details page. Useful when you display offers in your own UI.

js
sdk.openOffer('89864438b3');

Get Available Rewards ​

Use getRewards() to fetch all rewards. Unlike pending(), this returns rewards in all statuses.

js
const rewards = await sdk.getRewards();

// Each reward:
// {
//   rewardId: 123,
//   amount: 500,
//   description: "Task completed",
//   status: 1,       // 1 = available, 2 = pending, 3 = confirmed
//   hash: "abc123..."  // present when status = 1 (use for confirmReward)
// }
ts
interface RewardItem {
  rewardId: number | string;
  amount: number;
  description?: string;
  status: 1 | 2 | 3;    // 1 = available, 2 = pending, 3 = confirmed
  hash?: string;         // present when status = 1 (use for confirmReward)
}

const rewards: RewardItem[] = await sdk.getRewards();

Claim a Reward ​

Use claimReward(rewardId) to initiate a reward claim without opening the iframe. Changes reward status (1 → 2) and returns the hash for confirmation.

js
const result = await sdk.claimReward(123);

if (result.success) {
  // { success: true, rewardId: 123, amount: 500, hash: "abc123..." }
  await creditUserReward(userId, result.amount);
  await sdk.confirmReward(result.rewardId, result.hash);
}
ts
interface ClaimResult {
  success: boolean;
  rewardId?: number | string;
  amount?: number;
  hash?: string;
  error?: string;
}

const result: ClaimResult = await sdk.claimReward(123);

if (result.success && result.hash) {
  await creditUserReward(userId, result.amount!);
  await sdk.confirmReward(result.rewardId!, result.hash);
}

A rewardClaim event is also emitted on successful claim, consistent with the iframe flow.

Event Subscription System ​

The SDK provides an event system that allows you to react to various events in the OfferWall lifecycle.

Key Events ​

javascript
// SDK initialization
sdk.on('initialized', (data) => {
  console.log('SDK initialized:', data.userId, data.projectId);
});

// OfferWall loaded
sdk.on('loaded', () => {
  console.log('OfferWall loaded');
  hideLoadingIndicator();
});

// OfferWall opened
sdk.on('opened', () => {
  console.log('OfferWall opened');
  analytics.track('offerwall_opened');
});

// OfferWall closed
sdk.on('closed', () => {
  console.log('OfferWall closed');
  refreshUserBalance();
});

Unsubscribing from Events ​

javascript
const handleOpened = () => {
  console.log('OfferWall opened');
};

// Subscribe
sdk.on('opened', handleOpened);

// Unsubscribe
sdk.off('opened', handleOpened);

// Unsubscribe from all events
sdk.offAll();

Complete Integration Example ​

javascript
async function initSDK() {
  try {
    const sdk = await loadOfferWallSDK({
      projectId: 'your-project-id'
    });

    // Event handlers
    sdk.on('loaded', () => {
      hideLoadingIndicator();
    });

    sdk.on('opened', () => {
      trackEvent('offerwall_opened');
    });

    sdk.on('closed', () => {
      refreshUserData();
    });

    // Reward claim handler
    sdk.on('rewardClaim', async (data) => {
      await creditUserReward(data.userId, data.amount);
      sdk.confirmReward(data.rewardId, data.hash);
    });

    // Check for pending rewards
    await checkPendingRewards(sdk);

    // Update UI based on offer availability
    updateOfferWallButton(sdk);

    document.getElementById('offerwall-button').addEventListener('click', () => {
      sdk.open();
    });

    return sdk;
  } catch (error) {
    console.error('Error initializing SDK:', error);
    showErrorMessage('Failed to load offers. Please try again later.');
  }
}

async function checkPendingRewards(sdk) {
  const pendingRewards = await sdk.pending();

  if (pendingRewards.length > 0) {
    showNotification(`You have ${pendingRewards.length} unclaimed rewards!`);

    for (const reward of pendingRewards) {
      try {
        await creditUserReward(reward.userId, reward.amount);
        await sdk.confirmReward(reward.rewardId, reward.hash);
      } catch (error) {
        console.error('Error confirming reward:', error);
      }
    }

    refreshUserBalance();
  }
}

function updateOfferWallButton(sdk) {
  const button = document.getElementById('offerwall-button');

  if (sdk.hasOffers()) {
    button.style.display = 'block';
    button.disabled = false;
  } else {
    button.style.display = 'none';
    document.getElementById('no-offers-message').style.display = 'block';
  }
}

document.addEventListener('DOMContentLoaded', initSDK);