Configurer des publicités répondant au mécanisme d'incitation

Contenu

Pourquoi utiliser les annonces interstitielles activables ?

Pour maximiser les revenus tout en conservant une excellente expérience utilisateur, Vungle recommande l'utilisation d'interstitiels activables. Apparaissant lors des interruptions naturelles dans l'application, les interstitiels activables offrent des revenus élevés, en particulier si vous suivez notre recommandation pour les rendre impossibles à ignorer. Cet emplacement offre également une excellente expérience utilisateur, car les utilisateurs choisissent de regarder une vidéo et sont récompensés par quelque chose de valeur, comme de la monnaie virtuelle, du contenu premium ou des produits intégrés à l'application. Le montant et le type de récompense accordés à un utilisateur pour la visualisation d'une vidéo dépend entièrement à vous. Il existe deux façons d'y parvenir : les récompenses intégrés à l'application ou les rappels serveur-à-serveur.

Option recommandée : récompenses intégrées aux applications

Vue d'ensemble

Il s'agit d'une alternative à l'utilisation des rappels serveur-à-serveur. Lorsqu'un utilisateur visionne complètement une annonce ou clique sur le bouton de téléchargement, vous pouvez le récompenser directement dans votre application. Le principal avantage de cette approche est qu'elle est assez simple à mettre en œuvre. Si vous désirez quelque chose de rapide et ne craignez pas les attaques par rejeu, ceci devrait suffire.

Vungle offre désormais une variété de formats d'annonces avec des annonces-modèles dynamiques. Contrairement au format d'annonce traditionnel dans lequel la diffusion d'une annonce se compose d'une lecture vidéo suivie d'un écran de fin, nous proposons des modèles où le bouton d'appel à l'action (CTA) est disponible pendant la lecture vidéo. Les utilisateurs qui visionnent toute l'annonce vidéo, de même que ceux qui font clic sur le bouton, devraient être récompensés.

Mise en œuvre pour iOS

Implémentez le délégué VungleSDK, consultez pour ça la section « Rappels de délégué » de « Démarrer avec Vungle - iOS SDK v. 5.1 + ».

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

Si le rappel vungleSDKwillCloseAdWithViewInfo vous renvoie un dictionnaire viewInfo avec des valeurs « yes » pour les clés completedView ou didDownload, alors l'utilisateur a gagné une récompense.

Mise en œuvre pour Android

Implémentez l'interface EventListener, consultez « Démarrer avec Vungle - Android SDK v. 5.1 + ».

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

Si le rappel onAdEnd renvoie « true » pour wasSuccessfulView ou pour wasCallToActionClicked, alors l'utilisateur a gagné une récompense.

Rappels serveur-à-serveur

Vue d'ensemble

Les rappels de serveur-à-serveur permettent de récompenser les utilisateurs pour les visualisations d'annonces avec des monnaies intégrés au jeu ou d'autres récompenses. Lorsqu'un utilisateur visionne complètement une annonce, vous pouvez configurer un rappel des serveurs de Vungle vers le vôtre pour vous informer que l'utilisateur a terminé l'action.

L'un des avantages de cette approche est le contrôle. Cette méthode permet de faire des modifications et des mises à jour directement du côté du serveur, donc il n'est pas nécessaire d'effectuer une mise à jour par « push ». Un autre avantage est la sécurité dans la prévention des attaques par rejeu (lorsqu'une transmission de données valides est répétée ou retardée de façon malveillante ou frauduleuse).

Mise en œuvre pour Vungle SDK v. 5.1 +

À partir de SDK v. 5.1, nous avons déplacé l'option de récompense sur le tableau de bord, afin que les éditeurs puissent facilement la modifier sans changer le code. Dans votre tableau de bord, trouvez la case à cocher pour activer ou désactiver l'option de récompense au niveau de l'emplacement en faisant Application StagePlacements Stage → clic sur l'icône du crayon image1.png → Edit Placement.

image2.jpg

Mise en œuvre pour Vungle SDK v.1.0 - v.4.1

  • Pour iOS, commencez par définir l'option vunglePlayAdOptionKeyIncentivized de votre objet playAd à « yes ». (Retrouvez plus de détails sur cette option et sur les options de playAd associées ici.)

  • Pour Android, commencez par définir l'option setIncentivized de votre objet AdConfig à « true ». (Retrouvez plus de détails sur cette option et sur les options de playAd associées ici.)

Lorsqu'un utilisateur visionne au moins 80 % d'une annonce répondant au mécanisme d'incitation, cette dernière est considérée comme ayant été entièrement visionnée. Vungle effectue ensuite un « ping » sur vos serveurs avec une URL de rappel ressemblant à ceci :

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

ou à ceci :

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

Configurez l'URL de rappel dans les paramètres de votre application sur le tableau de bord (comme indiqué ci-dessous).

CallbackURL.gif

La plupart des éditeurs utilisent seulement %user%, plus%txid%, et %digest% pour la sécurité, mais tous les éléments suivants sont disponibles :

Variables

Description

%user%

Le nom d'utilisateur fourni par le SDK Vungle via :

●      iOS : la clé VunglePlayAdOptionKeyUser du dictionnaire des options transmis à playAd()

●      Android : le définisseur setIncentivizedUserId de l'objet de configuration globale de l'annonce transmis à playAd()

%udid%

Un identificateur unique pour l'appareil

%ifa%

●      iOS : un identificateur unique Apple pour l'appareil.

●      Android : ceci renvoie l'ID publicitaire Google

%txid%

Un ID de transaction unique pour la diffusion complète

%digest%

Jeton de sécurité pour vérifier que le rappel provient de Vungle ; consulter la section Sécurité pour plus de détails

Notez que %user% est la seule variable que vous devez passer. Les autres sont renvoyées à partir des serveurs de Vungle si vous les incluez dans l'URL de rappel.

Configuration des récompenses

Maintenant que vous pouvez récompenser un utilisateur pour avoir regardé une annonce, que lui donnez-vous ? Si vous donnez des gemmes à chaque fois, c'est plutôt simple. Mais que faire si vous voulez faire mieux ? Nous ne disposons pas d'une option intégrée pour la configuration de la récompense, mais voici ce que nous recommandons :

Exemple : Pièces de monnaie et vies

Supposons que votre application contienne à plusieurs niveaux des annonces répondant au mécanisme d'incitation : à la fois dans votre boutique et à la fin de toutes les troisièmes parties (« game-over »). Supposons aussi que vous souhaitiez récompenser le joueur avec des pièces de monnaie dans le magasin et avec une vie à la fin du jeu. Pour chaque instance de playAd(), configurez l'utilisateur comme ceci :

userName123:coins or userName123:lives 

Ensuite, lorsque vos serveurs reçoivent le rappel de Vungle, analysez la valeur de %user% pour savoir quelle récompense attribuer !

Sécurité

Authentification des rappels

Afin de vérifier que les rappels que vous recevez proviennent de Vungle, cochez la case « Secret Key for Secure Callback » pour votre application. Cela génère une clé secrète semblable à ceci :

4YjaiIualvm8/4wkMBRH8pctlqB1NyzhK3qUGUar+Zc=

Vous pouvez utiliser la clé pour vérifier l'origine du rappel comme suit :

  1. Créez la chaîne brute de vérification de la transaction en concaténant votre clé secrète avec l'ID de la transaction, les deux étant séparés par deux-points comme ceci :
    transactionString = secretKey + ":" + %txid%
  2. Hachez les octets de transactionString en utilisant deux fois l'algorithme SHA-256.

  3. Générez le jeton de vérification de la transaction en codant en hexadécimal les octets de sortie des deux hachages séquentiels SHA-256, et vous obtenez quelque chose comme ceci :
    transactionToken = 870a0d13da1f1670b5ba35f27604018aeb804d5e6ac5c48194b2358e6748e2a8
  4. Vérifiez que le jeton de transaction généré équivaut au jeton envoyé dans la chaîne de requête du rappel en tant %digest%.

Prévention des attaques par rejeu

Pour éviter qu'un seul rappel ne soit reproduit à plusieurs reprises par rapport à votre serveur, enregistrez les ID des transactions authentifiées et rejetez les rappels futurs ayant des ID de transaction en double. Étant donné qu'un ID de transaction contient un horodatage, vous pouvez limiter le nombre d'ID de transaction à stocker et vérifier les doublons en appliquant un temps d'arrêt pour les rappels opportuns comme suit :

  1. Faites l'extraction de l'horodatage (en millisecondes) à partir de l'ID de transaction comme ceci :
    transactionMillis = transactionId.substringAfter(":")
  2. Vérifiez que transactionMillis est plus grand que le temps d'arrêt et que transactionId n'a pas été rencontré depuis le temps d'arrêt.

Exemple de code

Ces bouts d'exemples de code vous aideront à programmer la sécurité des rappels serveur-à-serveur. Il y a des exemples pour : Node.js, Java, Python et 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) {
  // La transaction possède-t-elle un format raisonnable ?
  var tx_timestamp = getTransactionTimestamp(transaction_id);
  if (tx_timestamp === null) { return false; }

  // La transaction se situe-t-elle dans une plage de temps raisonnable ?
  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);
}

// La transaction a-t-elle été diffusée avant ?
// REMARQUE : Pour aller au-delà de ce seul processus de nœud, ceci doit être inséré dans
// une sorte de mémoire de données centralisé. Toute mémoire prenant en charge les insertions atomiques dans un ensemble
// devrait faire l'affaire. La base de données Redis, avec sa commande SADD, pourrait être bon pour un début.
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) {
      // format d'ID de transaction incorrect
    }
    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 si transactionId est nouveau, conserver cette variable avec la variable transactionMillis associée
    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);
      // horodatage incorrect
    }
  }

  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
Vous avez d’autres questions ? Envoyer une demande

Commentaires