Protect your rewards economy against spoofing attacks, forged payouts, and replay tampering using HMAC-SHA256 signature verification.
Every postback dispatched by AdVanta includes a cryptographic signature generated using your App's Secret Key (found in your Publisher App Settings).
X-AdVanta-Signature&sig=[SIGNATURE]HMAC-SHA256<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class AdVantaPostbackController extends Controller
{
public function handle(Request $request)
{
$appSecret = config('services.advanta.secret_key'); // Your private App Secret
$incomingSig = $request->header('X-AdVanta-Signature') ?? $request->query('sig');
if (!$incomingSig) {
return response('Missing signature', 400);
}
// Build verification payload from query params (excluding 'sig')
$params = $request->except('sig');
ksort($params);
$payload = http_build_query($params);
$expectedSig = hash_hmac('sha256', $payload, $appSecret);
// Timing-attack safe hash comparison
if (!hash_equals($expectedSig, $incomingSig)) {
Log::warning('AdVanta Postback signature mismatch!', ['expected' => $expectedSig, 'got' => $incomingSig]);
return response('Invalid signature', 403);
}
// Signature verified! Extract transaction parameters
$userId = $request->input('userId');
$payout = (float) $request->input('payout');
$reward = (float) $request->input('reward');
$txId = $request->input('txId');
$status = $request->input('status'); // 1 = approved, 2 = reversed/charged back
// Deduplicate against your transactions database
if (Transaction::where('tx_id', $txId)->exists()) {
return response('OK', 200); // Already credited, acknowledge with 200
}
if ($status == 1) {
User::where('id', $userId)->increment('balance', $reward);
Transaction::create([
'user_id' => $userId,
'tx_id' => $txId,
'amount' => $reward,
'payout' => $payout
]);
}
return response('OK', 200);
}
}
const express = require('express');
const crypto = require('crypto');
const app = express();
const APP_SECRET = process.env.ADVANTA_APP_SECRET;
app.get('/api/postback', (req, res) => {
const incomingSig = req.headers['x-advanta-signature'] || req.query.sig;
if (!incomingSig) {
return res.status(400).send('Missing signature');
}
// Sort query keys alphabetically excluding 'sig'
const query = { ...req.query };
delete query.sig;
const sortedKeys = Object.keys(query).sort();
const payload = sortedKeys.map(k => `${k}=${encodeURIComponent(query[k])}`).join('&');
const expectedSig = crypto.createHmac('sha256', APP_SECRET).update(payload).digest('hex');
if (incomingSig !== expectedSig) {
console.error('Invalid signature detected');
return res.status(403).send('Forbidden');
}
const { userId, reward, txId } = req.query;
console.log(`Credited user ${userId} with ${reward} points for transaction ${txId}`);
return res.status(200).send('OK');
});
To prevent replay attacks where a malicious actor re-sends an intercepted valid request:
txId in a unique index column in your database.timestamp={timestamp} in your postback URL and reject requests where abs(time() - $timestamp) > 300 (older than 5 minutes).