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.

Please contact TyrAds team and send your endpoint URL

HTTP Request Structure

Your endpoint will be invoked via HTTP request with GET method. The parameters of the request are as follows:

Paramater
Data Type
Description

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}
Component
Description

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...

The version prefix (v1, v2, v3, etc.) may vary. Your verification logic should handle different token versions.

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:

  1. Parse token components (version, keyId, timestamp, nonce, signature)

  2. Fetch your security key using the keyId

  3. Validate timestamp (tokens older than 5 minutes are rejected) (It depends upon your needs as well,

  4. Validate nonce (ensure it hasn't been used before to prevent replay attacks)

  5. Reconstruct the signing payload:

    • Extract all URL query parameters

    • Sort parameters alphabetically by key

    • Format: param1=value1&param2=value2&ts={timestamp}&nonce={nonce}

  6. Calculate HMAC-SHA256 signature with your secret key

  7. 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 False

FAQ

Is X-Tyrads-Token required?

While not mandatory, it's strongly recommended for enhanced security. Contact the TyrAds team to enable it for your account.

How often should I rotate my security key?

It depends on you if you want to rotate after n number of days. Same can be communicated to Tyrads

Can the same token be used twice?

No. Each token contains a unique nonce and timestamp. You should track used nonces (with TTL matching your timestamp validation window) to reject duplicate requests. Reusing tokens indicates a potential replay attack.

What if my key is compromised?

Immediately contact the TyrAds, to deactivate the compromised key and issue a new one.

For security key setup or issues, please reach out to the TyrAds team directly.

Last updated