Configuração de anúncios premiados

Conteúdo

Por que utilizar anúncios intersticiais com autorização?

Para maximizar a receita e manter um alto UX, a Vungle recomenda utilizar intersticiais com autorização. Exibidos como pausas naturais no aplicativo, os intersticiais com autorização proporcionam alta receita, especialmente se você seguir nossa recomendação de não permitir que sejam pulados. Esse posicionamento também proporciona um alto UX, porque os usuários aceitam assistir um vídeo e têm uma recompensa de valor, como uma moeda virtual, conteúdo premium ou bens no aplicativo. Você determina a quantidade e o tipo de recompensa do usuário por assistir todo um vídeo. Há duas maneiras de alcançar isso: recompensas no aplicativo ou chamadas de retorno de servidor-a-servidor.

Opção recomendada: recompensas no aplicativo

Visão geral

Essa é uma alternativa ao uso de retorno de chamadas servidor-a-servidor. Quando um usuário assiste todo um anúncio ou clica no botão Download, você pode recompensá-lo diretamente em seu aplicativo. O principal benefício dessa abordagem é que ela é muito simples de implementar. Se você busca algo rápido e não se preocupa com ataques de replay, essa é uma boa opção.

Agora o Vungle oferece diversos formatos de anúncio com os Modelos dinâmicos (Dynamic Template ads). Diferentemente do formato de anúncio tradicional, no qual a reprodução consiste em reproduzir um vídeo e depois um cartão final, oferecemos modelos com o botão de chamada para ação (CTA) já disponível durante a reprodução do vídeo. Os usuários que assistem todo o anúncio e os que clicam no botão devem ser recompensados.

Implementação para iOS

Implemente o VungleSDK Delegate, apresentado na seção “Retornos de chamadas delegados” da Introdução ao Vungle - iOS SDK v. 5.1 ou posterior.

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

Se o retorno de chamada vungleSDKwillCloseAdWithViewInfo passa para você um dicionário viewInfo com valores de ‘yes’ para as chaves completedView ou didDownload, o usuário ganhou a recompensa.

Implementação para Android

Implemente a interface EventListener, apresentada na Introdução ao Vungle - Android SDK v. 5.1 ou posterior.

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

Se o retorno de chamada onAdEnd retornar ‘true’ para wasSuccessfulView ou wasCallToActionClicked, o usuário ganhou a recompensa.

Retornos de chamada servidor-a-servidor

Visão geral

Os retornos de chamada servidor-a-servidor permitem a você recompensar os usuários que assistem anúncios com moeda nos jogos ou outras recompensas. Quando um usuário assiste todo um anúncio, você pode configurar um retorno de chamada dos servidores do Vungle para informar você que o usuário concluiu a ação.

Um benefício dessa abordagem é o controle. Esse método permite a você realizar alterações e atualizações diretamente do lado do servidor, sem necessidade de enviar uma atualização. Outro benefício é a segurança ao prevenir ataques de replay (quando uma transmissão de dados válida é maliciosa ou fraudulentamente repetida ou atrasada).

Implementação para Vungle SDK v. 5.1 ou posterior

A partir do SDK v. 5.1 nós mudamos a opção de recompensa para o painel, para que os editores possam alterar facilmente a opção sem alterar o código. Em seu painel, há uma caixa de seleção para ativar/desativar a opção de recompensa no nível do posicionamento em Estágio do aplicativoEstágio dos posicionamentos → clique no ícone do lápis image1.png → Editar posicionamento.

image2.jpg

Implementação para Vungle SDK v.1.0 a v.4.1

  • Para o iOS, comece definindo a opção vunglePlayAdOptionKeyIncentivized em seu objeto playAd como ‘yes’. (Leia mais detalhes desta opção, e das playAd opções relacionadas aqui.)

  • Para o Android, comece definindo a opção setIncentivized em seu objeto AdConfig como ‘true’. (Leia mais detalhes desta opção, e das playAd opções relacionadas aqui.)

Quando um usuário assiste 80% ou mais de um anúncio incentivado, isso é considerado uma visualização completa. O Vungle irá enviar um ping a seus servidores com um URL de retorno de chamada, semelhante a:

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

ou a:

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

Configure o URL de retorno de chamada nas Configurações do seu aplicativo, no painel (como mostrado abaixo).

CallbackURL.gif

A maioria dos editores só usa %user%, mais%txid% e %digest% por segurança, mas todos estes estão disponíveis:

Variables

Descrição

%user%

O nome do usuário fornecido ao SDK Vungle via:

●      iOS: a chave VunglePlayAdOptionKeyUser do dicionário de opções passado para playAd()

●      Android: o configurador setIncentivizedUserId do objeto global de config. do anúncio passado ao playAd()

%udid%

Um identificador exclusivo do dispositivo

%ifa%

●      iOS: um identificador exclusivo do dispositivo Apple.

●      Android: isso retorna o ID do Google Advertiser

%txid%

Um ID de transação exclusivo da visualização completa

%digest%

O token de segurança para confirmar que o retorno de chamada veio do Vungle. Veja detalhes na seção Segurança

Observe que %user% é a única variável que você tem de transmitir. O restante virá dos servidores do Vungle se você incluí-los no URL do retorno de chamada.

Configuração de recompensa

Agora que você pode recompensar um usuário por assistir o anúncio, o que dará a ele? Se você já der o prêmio a cada vez, isso não será estimulante. Mas e se você quiser avançar um pouco mais? Não temos uma opção incorporada para a configuração da recompensa, mas recomendamos:

Exemplo: moedas ou vidas

Digamos que seu aplicativo incentivou anúncios em diversos locais: na sua loja e a cada terceiro “game-over.” Você deseja recompensar o jogador com moedas na loja, e com vidas no jogo encerrado. Para cada instância de playAd(), configure o usuário assim:

userName123:coins or userName123:lives 

Depois, quando seus servidores receberem o retorno de chamada do Vungle, envie %user% para a recompensa correta.

Segurança

Autenticação de retorno de chamada

Para verificar se os retornos de chamada recebidos vieram do Vungle, marque a caixa de seleção Chave secreta para retorno de chamada seguro para seu aplicativo. Isso vai gerar uma chave secreta como esta:

4YjaiIualvm8/4wkMBRH8pctlqB1NyzhK3qUGUar+Zc=

Você pode usar a chave para confirmar a origem do retorno de chamada, assim:

  1. Crie a string de verificação de transação bruta concatenando sua chave secreta com o ID da transação, separados por dois-pontos, assim
    transactionString = secretKey + ":" + %txid%
  2. Verifique o código hash de transactionString duas vezes com o algoritmo SHA-256.

  3. Gere o token de verificação da transação codificando em hexadecimal os bytes de saída de duas rodas sequenciais do hash SHA-256, que vai ser algo assim:
    transactionToken = 870a0d13da1f1670b5ba35f27604018aeb804d5e6ac5c48194b2358e6748e2a8
  4. Verifique se o transactionToken que você gerou é igual ao enviado na string de consulta do retorno de chamada como %digest%.

Prevenção de ataques de Replay

Para impedir que um retorno de chamada seja reproduzido diversas vezes contra seu servidor, armazene IDS de transação autenticados e recuse futuros retornos de chamada com IDs de transação repetidos. Como um ID de transação contém uma marcação de hora, você pode limitar o número de IDs de transação que terá de armazenar e verificar se há duplicatas, fazendo um corte de retornos de chamada recorrentes, assim:

  1. Extraia a marca de hora (em milissegundos) do ID da transação, dessa maneira: 
    transactionMillis = transactionId.substringAfter(":")
  2. Verifique se transactionMillis é posterior ao seu limite e se transactionId não foi encontrado desde o limite.

Exemplo de código

Esses bits de código de amostra ajudarão você a implementar a segurança de retornos de chamada servidor-a-servidor. Temos exemplos para: Node.js, Java, Python e 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) {
  // O formato da transação é razoável?
  var tx_timestamp = getTransactionTimestamp(transaction_id);
  if (tx_timestamp === null) { return false; }

  // A faixa de horas da transação é razoável?
  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);
}

// Essa transação já foi vista antes?
// NOTA: para ampliar além desse processo de um nó, você terá de implantar algum
// tipo de armazenamento de dados centralizado. Qualquer um que suporte inserções atômicas em
// um conjunto é adequado. O banco de dados Redis, com seu comando SADD, é uma boa opção inicial.
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) {
      // formato inválido do ID da transação
    }
    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 se transactionId for novo, armazena o transactionId com o transactionMillis associado
    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);
      // marca de hora inválida
    }
  }

  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
Tem mais dúvidas? Envie uma solicitação

Comentários