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