Conteúdo
- Por que utilizar anúncios intersticiais com autorização?
- Opção recomendada: recompensas no aplicativo
- Retornos de chamada servidor-a-servidor
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.
Observação: Anúncios premiados são, às vezes, chamados de anúncios incentivados; ambos os termos sempre referem-se ao mesmo tipo de anúncio. No código do SDK e em nossa API de relatórios, usamos o termo "incentivado".
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
Observação para SDK do Vungle v. 5.x: A partir do SDK v. 5.1, 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 aplicativo → Estágio dos posicionamentos → clique no ícone do lápis → Editar posicionamento.
Observação para SDK do Vungle v.1.0 - v.4.1:
-
Para o iOS, comece definindo a opção
vunglePlayAdOptionKeyIncentivized
em seu objetoplayAd
como ‘yes’. (Leia mais detalhes desta opção, e dasplayAd
opções relacionadas aqui.)
-
Para o Android, comece definindo a opção
setIncentivized
em seu objetoAdConfig
como ‘true’. (Leia mais detalhes desta opção, e dasplayAd
opções relacionadas aqui.)
Implementação para todas as versões do SDK do Vungle
Quando um usuário assiste 80% ou mais de um anúncio premiado, 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%
Recomendamos o uso de etxid
e edigest
, assim:
http://acme.com/bugzBunny/reward? amount=1&uid=%user%&etxid=%etxid%&edigest=%edigest%
.
Configure o URL de retorno de chamada nas Configurações do seu aplicativo, no painel (como mostrado abaixo).
A maioria dos editores só usa %user%
, mais
%txid%
e %digest%
por segurança, mas todos estes estão disponíveis:
Variables |
Descrição |
|
O nome do usuário fornecido ao SDK Vungle via: ● iOS: a chave ● Android: o configurador |
|
Um identificador exclusivo do dispositivo |
|
● iOS: um identificador exclusivo do dispositivo Apple. ● Android: isso retorna o ID do Google Advertiser |
|
Um ID único de transação para a visualização concluída com data e hora do servidor; transactionID:timestamp (recomendado). |
|
O token de segurança para confirmar que o retorno de chamada veio do Vungle; consulte a seção Segurança para detalhes (recomendado). |
|
Um ID de transação exclusivo da visualização completa |
|
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 tenha premiado 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 deste modo:
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:
- 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% or %etxid%
- Verifique o código hash de
transactionString
duas vezes com o algoritmo SHA-256.
- 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
- Confirme se o
transactionToken
que você gerou é igual ao que enviou na string de consulta de retorno de chamada como%digest%
ou%edigest%
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:
- Extraia a marca de hora (em milissegundos) do ID da transação, dessa maneira:
transactionMillis = transactionId.substringAfter(":")
- Verifique se
transactionMillis
é posterior ao seu limite e setransactionId
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. Observe que:
- O
transaction_id
pode ser%txid%
ou%etxid%
(recomendado). - Para
%etxid%
, o hash de verificação deve ser%edigest%
.
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) {
// Does the transaction have a reasonable format?
var tx_timestamp = getTransactionTimestamp(transaction_id);
if (tx_timestamp === null) { return false; }
// Is the transaction within a reasonable time range?
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);
}
// Have we seen this transaction before?
// NOTE: To scale beyond just this one node process, you'll need to put this in some
// sort of centralized datastore. Any one that supports atomic insertions into a set
// should do. The Redis database, with its SADD command, would be a good place to start.
var known_transactions = {};
function isTransactionNew(transaction_id) {
// For %etxid%, extract unique event id from etxid, which is the string before “:”, and use it to identify unique ad events
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) {
// invalid transaction ID format
}
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 if transactionId is new, store the transactionId with its associated transactionMillis
// For %etxid%, extract unique event ID from etxid, which is the string before ":", and use it to identify unique ad events
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);
// invalid timestamp
}
}
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."""
"""For %etxid%, extract unique event ID from etxid, which is the string before ":", and use it to identify unique ad events"""
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
# For %etxid%, extract unique event ID from etxid, which is the string before ":", and use it to identify unique ad events
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
Comentários