Настройка стимулированной рекламы

Содержание

Использование рекламных вставок, демонстрируемых с согласия пользователя

Чтобы максимизировать доход, поддерживая при этом отличные показатели UX (оценка взаимодействия пользователей с приложением), в Vungle рекомендуется использовать рекламные вставки, демонстрируемые с согласия пользователя. Рекламные вставки, демонстрируемые с согласия пользователя во время естественных пауз в работе приложения, могут обеспечить получение высокого дохода, особенно если вы будете следовать нашим рекомендациям, позволяющим сделать эти вставки непропускаемыми. Подобное размещение рекламы также обеспечивает получение отличных показателей UX, поскольку пользователи, выражая согласие на просмотр видео, получают вознаграждение, которое может иметь различный вид – например, виртуальную валюту, премиальный контент или связанные с приложением товары. Сумма и тип вознаграждения, предоставляемого пользователю за просмотр видеоролика, полностью определяются вами. Имеется два способа реализации этого подхода: вознаграждения, встроенные в приложение, и обратный вызов типа сервер-сервер.

Рекомендуемый вариант: вознаграждения, встроенные в приложение

Общие сведения

Этот подход является альтернативным обратным вызовам типа сервер-сервер. Когда пользователь успешно выполняет просмотр рекламы или нажимает кнопку скачивания, вы можете предоставить ему вознаграждение непосредственно в приложении. Главное преимущество этого подхода состоит в простоте его реализации. Если вы осуществляете быстрый поиск и не опасаетесь атак с повторением пакетов, им имеет смысл воспользоваться.

В Vungle теперь предлагаются различные форматы рекламы, реализуемые с помощью рекламы с динамическим шаблоном. В отличие от традиционного формата рекламы, состоящего из воспроизведения видеоролика после которого выводится последний кадр, мы предлагаем шаблоны, где кнопка призыва к действию (CTA, call-to-action) доступна во время воспроизведения видео. Вознаграждение получат и пользователи, просмотревшие видео, и пользователи, нажавшие эту кнопку.

Реализация для iOS

Реализуйте делегата VungleSDK, обсуждавшегося в разделе "Делегирование обратных вызовов" в части "Начало работы с Vungle – iOS SDK v. 5.1 + и более поздними версиями".

- (void)vungleWillCloseAdWithViewInfo:(VungleViewInfo *)info placementID:(NSString *)placementID;

Если обратный вызов vungleSDKwillCloseAdWithViewInfo передает вам словарь viewInfo со значениями "yes" для ключей completedView или didDownload, пользователь зарабатывает вознаграждение.

Реализация для Android

Реализуйте интерфейс EventListener, обсуждавшийся в разделе "Начало работы с Vungle – Android SDK v. 5.1 + и более поздними версиями".

public void onAdEnd (String placementReferenceId, boolean wasSuccessfulView, boolean wasCallToActionClicked)

Если обратный вызов onAdEnd возвращает "true" для wasSuccessfulView или wasCallToActionClicked, пользователь зарабатывает вознаграждение.

Обратные вызовы типа сервер-сервер

Общие сведения

Обратные вызовы типа сервер-сервер позволяют вам вознаграждать пользователей за просмотр рекламы "игровой" валютой или вознаграждениями другого типа. Когда пользователь успешно осуществляет просмотр рекламы, вы можете настроить обратный вызов из серверов Vungle к вашим серверам, чтобы уведомить вас о действии, выполненном пользователем.

Одно из преимуществ данного подхода состоит в управляемости. Этот метод позволяет вносить изменения и обновления непосредственно на стороне сервера, поэтому не требуется выполнять распространение обновлений. Другое преимущество связано с предотвращением атак с повторением пакетов (когда передача допустимых данных злонамеренно или обманным путем повторяется или задерживается).

Реализация для Vungle SDK v. 5.1 + и более поздних версий

Начиная с SDK v. 5.1, мы переместили параметр вознаграждения на панель управления, поэтому публикаторы могут менять этот параметр, не внося изменения в код. Найдите на панели управления флажок, позволяющий включать или отключать параметр вознаграждения на уровне размещений, выполнив следующий переход Application Stage (Стадия приложения) → Placements Stage (Стадия размещений) → щелкните значок карандаша image1.png → Edit Placement (Изменение размещения).

image2.jpg

Реализация для Vungle SDK v.1.0 – v.4.1

  • Для iOS начните с задания для параметра vunglePlayAdOptionKeyIncentivized в объекте playAd значения "yes". (Ознакомьтесь с дополнительными сведениями об этом параметре и связанных с ним параметрах playAd здесь.)

  • Для Android начните с задания для параметра setIncentivized в объекте AdConfig значения "true". (Ознакомьтесь с дополнительными сведениями об этом параметре и связанных с ним параметрах playAd здесь.)

Когда пользователь просматривает 80 % и более от объема стимулированной рекламы, этот просмотр считается выполненным. Vungle затем направляет вашим серверам URL-адрес обратного вызова, который может выглядеть примерно так:

http://acme.com/bugzBunny/reward?uid=%user%

или так:

http://acme.com/bugzBunny/reward? amount=1&uid=%user%&txid=%txid%&digest=%digest%

Настройте URL-адрес обратного вызова в параметре Настройки вашего приложения на панели управления (как показано ниже).

CallbackURL.gif

Большинство публикаторов используют только %user%, plus%txid% и %digest% для безопасности, однако доступны и все следующие элементы:

Переменные

Описание

%user%

Значение username, передаваемое в Vungle SDK с помощью:

●      iOS: ключ VunglePlayAdOptionKeyUser словаря параметров, передаваемый playAd()

●      Android: метод задания setIncentivizedUserId глобального объекта настройки рекламы, передаваемый playAd()

%udid%

Уникальный идентификатор для устройства

%ifa%

●      iOS: уникальный идентификатор для устройства Apple.

●      Android: будет возвращаться идентификатор рекламодателя Google

%txid%

Уникальный идентификатор операции для выполненного просмотра

%digest%

Маркер безопасности для проверки того, что обратный вызов пришел от Vungle; дополнительные сведения см. в разделе Безопасность

Обратите внимание, что %user% является единственной переменной, которую требуется передать. Остальные будут получены с серверов Vungle, если вы включили их в URL-адрес обратного вызова.

Конфигурация вознаграждения

Теперь вы можете вознаграждать пользователей за просмотр рекламы. Что вы им можете предложить? Если вы будете каждый раз предлагать бонусы, это может выглядеть несколько прямолинейно и примитивно. Однако вы можете проявить и несколько больше изобретательности. В продукт не был встроен параметр для настройки вознаграждения, однако мы рекомендуем следующее:

Пример: монеты или "жизни"

Предположим, что в вашем приложении предусмотрена стимулированная реклама в нескольких местах: и в вашем магазине, и в каждом третьем окончании игры. И вы хотите наградить игрока деньгами в магазине и "жизнями" при окончании игры. Для каждого экземпляра playAd() настройте пользователя следующим образом:

userName123:coins or userName123:lives 

Затем, когда серверы получат обратный вызов Vungle, проанализируйте %user% для определения правильного вознаграждения!

Безопасность

Проверка подлинности обратных вызовов

Для проверки того, что источником полученных обратных вызовов является Vungle, выберите секретный ключ для флажка безопасного обратного вызова в вашем приложении. При этом будет создаваться примерно такой секретный ключ:

4YjaiIualvm8/4wkMBRH8pctlqB1NyzhK3qUGUar+Zc=

Вы можете использовать секретный ключ для проверки источника обратного вызова следующим образом:

  1. Создайте строку проверки входящей операции, связав свой секретный ключ с идентификатором операции, отделенным от него двоеточием, например:
    transactionString = secretKey + ":" + %txid%
  2. Хэшируйте байты transactionString дважды с помощью алгоритма SHA-256.

  3. Создайте маркер проверки операции, выполнив шестнадцатеричную кодировку выходных байтов двух последовательных кругов хэширования по алгоритму SHA-256. Полученный результат будет выглядеть примерно так:
    transactionToken = 870a0d13da1f1670b5ba35f27604018aeb804d5e6ac5c48194b2358e6748e2a8
  4. Проверьте, что созданный transactionToken равен значению, отправленному в строке запроса обратного вызова как %digest%.

Предотвращение атак с повторением пакетов

Чтобы предотвратить выполнение вашим сервером нескольких ответов на единичный обратный вызов, сохраняйте идентификаторы операций, прошедших проверку подлинности, и отклоняйте будущие обратные вызовы с дублированными идентификаторами операций. Поскольку идентификатор операции содержит отметку времени, вы можете ограничить число идентификаторов операции, которые необходимо хранить и проверять на дублируемость, задав следующим образом отсечку для обратных вызовов по времени:

  1. Извлеките отметку времени (в миллисекундах) из идентификатора операции следующим образом: 
    transactionMillis = transactionId.substringAfter(":")
  2. Проверьте, что значение transactionMillis больше заданной вами отсечки и что значение transactionId не было зарегистрировано с момента отсечки.

Пример кода

Эти фрагменты кода помогут вам при реализации безопасности обратных вызовов типа сервер-сервер! Здесь приводятся примеры для: Node.js, Java, Python и Ruby.

Node.js

var crypto = require('crypto');

function isTransactionValid(secret, transaction_id, provided_hash) {
  return isTransactionRecent(transaction_id) &&
         isTransactionNew(transaction_id)    &&
         createSecurityDigest(secret, transaction_id) === provided_hash;
}

function getTransactionTimestamp(transaction_id) {
  return parseInt(transaction_id.split(":")[1], 10) || null;
}

function isTransactionRecent(transaction_id) {
  // Операция имеет допустимый формат?
  var tx_timestamp = getTransactionTimestamp(transaction_id);
  if (tx_timestamp === null) { return false; }

  // Операция осуществляется в пределах допустимого диапазона времени?
  var now = new Date().getTime();
  var time_diff = now - tx_timestamp;
  var hour_in_future = -1000 * 60 * 60, three_days_ago = 1000 * 60 * 60 * 24 * 3;
  return (time_diff < three_days_ago && time_diff > hour_in_future);
}

// Встречалась ли эта операция раньше?
// ПРИМЕЧАНИЕ. Чтобы выполнить масштабирование за пределами этого процесса узла, необходимо
// поместить его в своего рода централизованное хранилище данных. Должны выполняться методы, поддерживающие атомарную вставку
// в набор. Для начала можно воспользоваться базой данных Redis и ее командой SADD.
var known_transactions = {};
function isTransactionNew(transaction_id) {
  if (known_transactions[transaction_id]) { return false; }

  known_transactions[transaction_id] = true;
  return true;
}

function createSecurityDigest(secret, transaction_id) {
  var firsthash = crypto.createHash("sha256").update(secret + ":" + transaction_id).digest("binary");
  return crypto.createHash("sha256").update(firsthash,"binary").digest("hex");
}

Java

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;

public class ServerCallbackSecurityExample {
  private static final long MAX_CALLBACK_AGE_MILLIS = 7 * 24 * 60 * 60 * 1000;  // 7 days

  /**
   * Checks that a transaction is recent enough, signed with the secretKey, and not a duplicate.
   * 
   * @param transactionId the transaction ID.
   * @param secretKey a shared secret.
   * @param verificationDigest the verification digest sent in the callback querystring.
   */
  public boolean isValidTransaction(String transactionId, String secretKey, String verificationDigest) throws NoSuchAlgorithmException {
    return isRecentTransaction(transactionId)
      && isDigestValid(transactionId, secretKey, verificationDigest)
      && isNewTransaction(transactionId);
  }

  protected boolean isRecentTransaction(String transactionId) {
    boolean isRecent = false;
    try {
      final long minCallbackAgeMillis = System.currentTimeMillis() - MAX_CALLBACK_AGE_MILLIS;
      final long transactionMillis = getTransactionMillis(transactionId);
      isRecent = (transactionMillis > minCallbackAgeMillis);
    }
    catch (ParseException exception) {
      // недопустимый формат идентификатора операции
    }
    return isRecent;
  }

  protected boolean isDigestValid(String transactionId, String secretKey, String verificationDigest) throws NoSuchAlgorithmException {
    return createSecurityDigest(transactionId, secretKey)
      .equals(verificationDigest);
  }

  protected boolean isNewTransaction(String transactionId) {
    // TODO, если идентификатор транзакции transactionId является новым, сохранить transactionId с соответствующим значением transactionMillis
    return true;
  }

  protected long getTransactionMillis(String transactionId) throws ParseException {
    final int transactionMillisIndex = transactionId.lastIndexOf(":") + 1;
    try {
      if (transactionMillisIndex > 0 && transactionMillisIndex < transactionId.length()) {
        return Long.parseLong(
          transactionId.substring(transactionMillisIndex));
      }
      else {
        throw new ParseException("No timestamp in transaction ID", transactionMillisIndex);
      }
    }
    catch (NumberFormatException exception) {
      throw new ParseException("Invalid transaction ID timestamp", transactionMillisIndex);
      // недопустимая отметка времени
    }
  }

  protected String createSecurityDigest(String transactionId, String secretKey) throws NoSuchAlgorithmException {
    final String verificationString = secretKey + ":" + transactionId;
    final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    return toHexString(
      messageDigest.digest(
        messageDigest.digest(
          verificationString.getBytes(Charset.forName("US-ASCII")))));
  }

  protected String toHexString(byte[] bytes) {
    final StringBuffer hexStringBuffer = new StringBuffer();
    for (final byte byt : bytes) {
      hexStringBuffer.append(
        Integer.toString((byt & 0xff) + 0x100, 16)
          .substring(1));
    }
    return hexStringBuffer.toString();
  }
}

Python

import hashlib, time

def isTransactionValid(secret, transaction_id, input_hash):
  """Returns whether this transaction id / hash should be considered valid"""
  return isTransactionIDRecent(transaction_id) and \
         isTransactionIDNew(transaction_id)    and \
         createSecurityDigest(secret, transaction_id) == input_hash

def getTransactionTimestamp(transaction_id):
  """Will return the unix time (in milliseconds) of this transaction, or None if invalid"""
  parsed = transaction_id.split(":")
  try:
    return int(parsed[1]) if len(parsed) == 2 else None
  except ValueError as e:
    return None

def isTransactionIDRecent(transaction_id):
  """Is this transaction within a reasonable time range?"""
  tx_time = getTransactionTimestamp(transaction_id)

  # Handle bad transaction:
  if tx_time is None:
    return False

  # Handle bad transaction times:
  now = int(time.time() * 1000)
  three_days_ago = now - (1000 * 60 * 60 * 24 * 3)
  one_hour_from_now = now + (1000 * 60 * 60)
  return ( three_days_ago < tx_time < one_hour_from_now )

def isTransactionIDNew(transaction_id, known_transaction_ids=set()):
  """Is this a duplicate transaction?
     NOTE: We only use the Python set for simplicity. For better / more centralized solutions,
     you can use any datastore that supports atomic insertion into a set. For starters, try
     Redis with its "SADD" command."""
  if transaction_id in known_transaction_ids:
    return False

  # Else, valid:
  known_transaction_ids.add(transaction_id)
  return True


def createSecurityDigest(secret, transaction_id):
  """Will return the string that the security hash should have been"""
  firsthash = hashlib.sha256()
  firsthash.update(secret + ":" + transaction_id)

  secondhash = hashlib.sha256()
  secondhash.update(firsthash.digest())

  return secondhash.hexdigest()

Ruby

require "openssl"
require "digest/sha2"
require "base64"
require "time"

# just some helper methods, ignore if using Rails
class Fixnum
  SECONDS_IN_DAY = 24 * 60 * 60
  HOURS_IN_DAY = 24 * 60

  def days
    self * SECONDS_IN_DAY
  end

  def hour
    self * 60 * 60
  end

  def ago
    (Time.now - self)
  end

  def from_now
    (Time.now + self)
  end
end

def transaction_valid?(secret, txid, input_hash)
  transaction_id_recent?(txid) && transaction_id_new?(txid) && create_security_digest(secret, txid) == input_hash
end

def transaction_timestamp(txid)
  arr = txid.split(":")
  return arr[1].to_i if arr.size == 2
end

def transaction_id_recent?(txid)
  tx_time = transaction_timestamp(txid)
  return false if tx_time.nil?

  now = Time.now.to_i
  three_days_ago = 3.days.ago
  one_hour_from_now = 1.hour.from_now
  three_days_ago.to_i < tx_time && tx_time < one_hour_from_now.to_i
end

def transaction_id_new?(txid, transactions = [])
  return false if transactions.include?(txid)
  transactions << txid
  return true
end

def create_security_digest(secret, txid)
  verification_string = "#{secret}:#{txid}"
  first_digest = Digest::SHA2.new(256).update(verification_string)
  Digest::SHA2.new(256).hexdigest(first_digest.digest)
end
Была ли эта статья полезной?
Пользователи, считающие этот материал полезным: 0 из 1
Еще есть вопросы? Отправить запрос

Комментарии