Postback Payout (S2S)
In order for users to redeem their accumulated rewarded plays, they must be disbursed. Each reward can only be claimed once.
Server to Server (S2S) Payout
To utilize server-to-server payout, you must establish an endpoint on your server. This endpoint will be requested by us to notify you of the user's rewards. Upon receiving the notification, it will be your responsibility to deliver it to the user.
We strongly advise implementing server-to-server payout for rewards, as it offers increased security and enhanced transparency on your part.
HTTP Request Structure
Your endpoint will be invoked via HTTP request with GET method. The parameters of the request are as follows:
conversion_status
enum
The status of conversion. Enum: approved, rejected
conversion_type
enum
The type of conversion: Enum: install, event, rewardedPlay
cost
decimal
The payout amount you earn from Tyrads in U.S. Dollars.
user_payout_converted
decimal
The payout amount the user earns based on the currency conversion set.
timestamp
integer
The UNIX timestamp of the conversion
publisher_user_id
string
The unique user id of the publisher used to identify the user
conversion_id
integer
The unique conversion ID can be used for deduplication if the conversion_type is install or event. This parameter shouldn't be used if conversion_type is rewardedPlay , as for rewarded play conversion, we will always send the conversion ID of the install
rewarded_play_id
string
The unique rewarded play conversion ID can be used for deduplication if conversion_type is rewardedPlay. This parameter shouldn't be used if the conversion_type is install or event
postback_id
integer
The unique postback ID can be used for deduplication. Increment value for install and event type is different from rewardedPlay type, the postback ID from rewardedPlay type can be duplicated with the postback ID from install and event type
app_name
string
The name of the app
event_name
string
The name of the event
sub3
string
The sub3 value, this can be any value you want and can be used to send to us on the click.
sub4
string
The sub4 value, this can be any value you want and can be used to send to us on the click.
ad_unit_id
string
The unique ad unit identifier
Security
Verification Token
To verify the authenticity of incoming requests, kindly reach out to the TyrAds team. Upon request, we will provide you with a verification token that you can add in your postback, enabling you to confidently authenticate requests coming from our platform.
To verify the authenticity of incoming requests, kindly reach out to the TyrAds team. Upon request, we will provide you with the verification token to be added into the Postback, enabling you to authenticate data originating from our platform.
Enhanced Security with X-Tyrads-Token
Overview
The X-Tyrads-Token header provides cryptographic verification for postback requests, ensuring that data has not been tampered with and originates from TyrAds. This enhancement adds an extra layer of security beyond the basic verification token.
How It Works
When TyrAds sends a postback to your endpoint, we include an X-Tyrads-Token header containing a digitally signed token. This token is generated using HMAC-SHA256 and is unique to each request.
Key Security Features
Data Integrity: Detects any modification to URL parameters
Authenticity: Verifies requests originate from TyrAds
Replay Protection: Timestamp prevents reusing old tokens
Uniqueness: Each token contains a unique nonce
Token Structure
{version}.kid={keyId}.ts={timestamp}.nonce={nonce}.sig={signature}version
Token version (v1, v2, v3, etc.)
kid
Key identifier for your publisher account
ts
Unix timestamp (seconds)
nonce
32-character hex string for uniqueness
sig
64-character HMAC-SHA256 signature
Setup
1. Enable X-Tyrads-Token
Contact the TyrAds team to:
Enable X-Tyrads-Token for your publisher account
Receive your encrypted security key
Get your key ID & Version
2. Request Structure
GET https://your-server.com/postback?user_id=12345&event=purchase&amount=99.99&type=event
Headers:
X-Tyrads-Token: v1.kid=1.ts=1700000000.nonce=a1b2...sig=d4e5...Implementation
Basic Verification Flow
async function handlePostback(req, res) {
// 1. Extract token from header
const token = req.headers['X-Tyrads-Token'];
if (!token) {
return res.status(401).json({ error: 'Missing X-Tyrads-Token' });
}
// 2. Get full URL
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
// 3. Verify token
const isValid = await verifyToken(token, fullUrl);
if (!isValid) {
return res.status(403).json({ error: 'Invalid token' });
}
// 4. Process postback
await processPostback(req.query);
return res.status(200).json({ success: true });
}Token Verification
The verification algorithm:
Parse token components (version, keyId, timestamp, nonce, signature)
Fetch your security key using the keyId
Validate timestamp (tokens older than 5 minutes are rejected) (It depends upon your needs as well,
Validate nonce (ensure it hasn't been used before to prevent replay attacks)
Reconstruct the signing payload:
Extract all URL query parameters
Sort parameters alphabetically by key
Format:
param1=value1¶m2=value2&ts={timestamp}&nonce={nonce}
Calculate HMAC-SHA256 signature with your secret key
Compare signatures using timing-safe comparison
Node.js Verification Example
const crypto = require('crypto');
// In-memory nonce store (use Redis or database in production)
const usedNonces = new Set();
async function verifySignedToken(token, url) {
try {
// Parse token
const parts = token.split('.');
if (parts.length !== 5 || !parts[0].startsWith('v')) {
return false;
}
const version = parts[0];
const keyId = parts[1].replace('kid=', '');
const timestamp = parts[2].replace('ts=', '');
const nonce = parts[3].replace('nonce=', '');
const providedSignature = parts[4].replace('sig=', '');
// Fetch your decrypted secret key (implement this based on your storage)
const secretKey = await getSecretKey(keyId);
if (!secretKey) return false;
// Validate timestamp (reject tokens older than 5 minutes)
const currentTimestamp = Math.floor(Date.now() / 1000);
const tokenTimestamp = parseInt(timestamp, 10);
const maxAgeSeconds = 300; // 5 minutes (Depends on your needs)
if (isNaN(tokenTimestamp) || currentTimestamp - tokenTimestamp > maxAgeSeconds) {
return false; // Token expired or invalid timestamp
}
// Validate nonce (prevent replay attacks)
if (usedNonces.has(nonce)) {
return false; // Nonce already used
}
// Extract and sort URL parameters
const urlObj = new URL(url);
const params = Array.from(urlObj.searchParams.entries())
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([k, v]) => `${k}=${v}`)
.join('&');
// Reconstruct signing payload
const payload = `${params}&ts=${timestamp}&nonce=${nonce}`;
// Calculate expected signature
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(payload)
.digest('hex');
// Timing-safe comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(providedSignature)
);
// Store nonce if valid (with TTL in production)
if (isValid) {
usedNonces.add(nonce);
// Clean up expired nonces periodically
setTimeout(() => usedNonces.delete(nonce), maxAgeSeconds * 1000);
}
return isValid;
} catch (error) {
console.error('Token verification error:', error);
return false;
}
}PHP Verification Example
<?php
// Use Redis or database in production
$usedNonces = [];
function verifySignedToken($token, $url) {
global $usedNonces;
// Parse token
$parts = explode('.', $token);
if (count($parts) !== 5 || strpos($parts[0], 'v') !== 0) {
return false;
}
$version = $parts[0];
$keyId = str_replace('kid=', '', $parts[1]);
$timestamp = str_replace('ts=', '', $parts[2]);
$nonce = str_replace('nonce=', '', $parts[3]);
$providedSignature = str_replace('sig=', '', $parts[4]);
// Fetch your secret key
$secretKey = getSecretKey($keyId);
if (!$secretKey) return false;
// Validate timestamp (reject tokens older than 5 minutes)
$currentTimestamp = time();
$tokenTimestamp = intval($timestamp);
$maxAgeSeconds = 300; // 5 minutes (Depends on your needs)
if ($currentTimestamp - $tokenTimestamp > $maxAgeSeconds) {
return false; // Token expired
}
// Validate nonce (prevent replay attacks)
if (in_array($nonce, $usedNonces)) {
return false; // Nonce already used
}
// Extract and sort URL parameters
$urlParts = parse_url($url);
parse_str($urlParts['query'], $params);
ksort($params);
$paramString = http_build_query($params);
// Reconstruct signing payload
$payload = "$paramString&ts=$timestamp&nonce=$nonce";
// Calculate expected signature
$expectedSignature = hash_hmac('sha256', $payload, $secretKey);
// Timing-safe comparison
$isValid = hash_equals($expectedSignature, $providedSignature);
// Store nonce if valid
if ($isValid) {
$usedNonces[] = $nonce;
// Clean up expired nonces periodically in production
}
return $isValid;
}
?>Python Verification Example
import hmac
import hashlib
import time
from urllib.parse import urlparse, parse_qs, urlencode
from typing import Set
# Use Redis or database in production
used_nonces: Set[str] = set()
async def verify_signed_token(token: str, url: str) -> bool:
try:
# Parse token
parts = token.split('.')
if len(parts) != 5 or not parts[0].startswith('v'):
return False
version = parts[0]
key_id = parts[1].replace('kid=', '')
timestamp = parts[2].replace('ts=', '')
nonce = parts[3].replace('nonce=', '')
provided_signature = parts[4].replace('sig=', '')
# Fetch your secret key
secret_key = await get_secret_key(key_id)
if not secret_key:
return False
# Validate timestamp (reject tokens older than 5 minutes)
current_timestamp = int(time.time())
token_timestamp = int(timestamp)
max_age_seconds = 300 # 5 minutes (Depends on your needs)
if current_timestamp - token_timestamp > max_age_seconds:
return False # Token expired
# Validate nonce (prevent replay attacks)
if nonce in used_nonces:
return False # Nonce already used
# Extract and sort URL parameters
parsed = urlparse(url)
params = parse_qs(parsed.query)
# Flatten single-value lists and sort
flat_params = {k: v[0] if len(v) == 1 else v for k, v in params.items()}
sorted_params = urlencode(sorted(flat_params.items()))
# Reconstruct signing payload
payload = f"{sorted_params}&ts={timestamp}&nonce={nonce}"
# Calculate expected signature
expected_signature = hmac.new(
secret_key.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Timing-safe comparison
is_valid = hmac.compare_digest(expected_signature, provided_signature)
# Store nonce if valid
if is_valid:
used_nonces.add(nonce)
# Clean up expired nonces periodically in production
return is_valid
except Exception as e:
print(f"Token verification error: {e}")
return FalseFAQ
Last updated