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