Werbungen mit Anreiz einrichten

Inhalt

Warum verwendet man zwischengeschaltete Werbungen mit Opt-In?

Zur Gewinnmaximierung bei gleichzeitig guter Nutzererfahrung empfiehlt Vungle die Verwendung von zwischengeschalteter Werbung mit Opt-In. Zwischengeschaltete Werbungen mit Opt-In werden zum Zeitpunkt natürlicher Pausen in der App angezeigt und bieten einen hohen Umsatz, insbesondere wenn Sie unsere Empfehlung befolgen, die Werbungen als nicht überspringbar zu gestalten. Diese Platzierung bietet auch eine gute Nutzererfahrung, da Nutzer zum Ansehen des Videos einen Opt-In vornehmen und durch etwas Wertvolles belohnt werden wie z.B. durch virtuelle Währung, Premium-Inhalte oder Güter innerhalb der App. Die Menge und der Typ der Belohnung, die an den Nutzer für das vollständige Ansehen eines Videos weitergegeben wird, kann von Ihnen frei gewählt werden. Sie können dies auf zwei Weisen bewerkstelligen: Mit Belohnungen innerhalb der App oder Server-zu-Server-Callbacks.

Empfohlene Möglichkeit: Belohnungen innerhalb der App

Überblick

Dies stellt eine Alternative zur Verwendung von Server-zu-Server Callbacks dar. Wenn ein Nutzer erfolgreich ein vollständiges Video ansieht oder auf die Download-Schaltfläche klickt, können Sie ihn direkt in der App belohnen. Der Hauptvorteil von diesem Ansatz liegt in der recht einfachen Implementierung. Wenn Sie nach etwas Schnellem suchen und nicht wegen Replay-Angriffen besorgt sind, ist diese Methode gut geeignet.

Vungle bietet inzwischen eine Vielzahl an Werbeformaten mit Dynamic Template-Werbungen. Gegenüber dem klassischen Werbeformat, bei dem eine Werbewiedergabe aus einer Videowiedergabe gefolgt von einer End-Karte besteht, bieten wir Vorlagen, bei denen der Call-to-Action (Aufruf zur Aktion, CTA) während der Videowiedergabe bereitsteht. Nutzer, die die Videowerbung vollständig abschließen sowie solche Nutzer, die auf die Schaltfläche klicken, sollten belohnt werden.

Implementierung für iOS

Implementieren Sie den VungleSDK-Delegierten wie im Abschnitt "Delegierte Callbacks" unter "Erste Schritte mit Vungle – iOS SDK V. 5.1 +" beschrieben.

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

Wenn der vungleSDKwillCloseAdWithViewInfo-Callback Ihnen ein viewInfo-Wörterbuch mit den Werten "yes" für die Schlüssel completedView oder didDownload zurückgibt, hat sich der Nutzer die Belohnung verdient.

Implementierung für Android

Implementieren Sie die EventListener-Schnittstelle wie unter Erste Schritte mit Vungle – Android SDK V. 5.1 + beschrieben.

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

Wenn der Callback onAdEnd für wasSuccessfulView oder wasCallToActionClicked "true" zurückgibt, hat der Nutzer sich die Belohnung verdient.

Server-zu-Server-Callbacks

Überblick

Server-zu-Server-Callbacks ermöglichen Ihnen die Belohnung von Nutzern für das Ansehen von Werbungen durch Spiel-Währung oder andere Belohnungen. Wenn ein Nutzer erfolgreich eine Werbung vollständig angesehen hat, können Sie einen Callback vom Vungle Server zu Ihrem eigenen einrichten, um Sie über die abgeschlossene Aktion des Nutzers zu benachrichtigen.

Ein Vorteil von diesem Ansatz ist die Kontrolle. Diese Methode ermöglicht Ihnen das Vornehmen von Änderungen und Aktualisierungen direkt auf der Serverseite, sodass keine Aktualisierungen gepusht werden müssen. Ein weiterer Vorteil ist die Sicherheit, da Replay-Attacken vorgebeugt wird (wenn eine gültige Übertragung fälschlich oder betrügerisch wiederholt oder verzögert wird).

Implementierung für Vungle SDK V. 5.1 +

Seit dem SDK V. 5.1 haben wir die Belohnungsoption auf das Dashboard verschoben, sodass Herausgeber die Option einfach ohne Code-Änderungen anpassen können. Suchen Sie in Ihrem Dashboard nach dem Kästchen zur Aktivierung oder Deaktivierung der Belohnungsoption auf der Platzierungsebene. Navigieren Sie hierfür zur AnwendungsebenePlatzierungen-Ebene → und klicken Sie auf das Bleistift-Symbol image1.png → Platzierung bearbeiten.

image2.jpg

Implementierung für Vungle SDK V.1.0 – V.4.1

  • Beginnen Sie für iOS mit der Einstellung der vunglePlayAdOptionKeyIncentivized-Option in Ihrem playAd-Objekt auf "yes". (Weitere Informationen über diese Option und über ähnliche playAd-Optionen erhalten Sie hier.)

  • Beginnen Sie für Android mit der Einstellung der setIncentivized-Option in Ihrem AdConfig-Objekt auf "true". (Weitere Informationen über diese Option und über ähnliche playAd-Optionen erhalten Sie hier.)

Wenn der Nutzer mindestens 80 % einer Werbung mit Anreiz ansieht, wird dies als vollständiges Ansehen betrachtet. Vungle pingt daraufhin Ihre Server mit einer Callback-URL an, die ungefähr so aussieht:

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

oder so:

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

Konfigurieren Sie die Callback-URL in den Einstellungen Ihrer App auf dem Dashboard (wie unten dargestellt).

CallbackURL.gif

Die meisten Herausgeber verwenden ausschließlich %user%, plus%txid% und %digest% zwecks der Sicherheit. Allerdings stehen alle nachfolgenden Optionen zur Verfügung:

Variablen

Beschreibung

%user%

Der Nutzername, der dem Vungle SDK wie folgt bereitgestellt wird:

●      iOS: Der VunglePlayAdOptionKeyUser-Schlüssel des Optionswörterbuchs, der an playAd() weitergeben wird

●      Android: Der setIncentivizedUserId-Setter des global ad config-Objekts, der an playAd() weitergeben wird

%udid%

Ein einzigartiger Identifikator für das Gerät

%ifa%

●      iOS: Der einzigartige Identifikator für das Gerät von Apple.

●      Android: Dies gibt die Google Advertiser ID zurück

%txid%

Eine einzigartige Transaktions-ID für das vollständige Ansehen

%digest%

Sicherheits-Token zur Bestätigung, dass der Callback von Vungle stammt; weitere Informationen erhalten Sie im Abschnitt Sicherheit.

Beachten Sie, dass %user% die einzige Variable darstellt, die Sie angeben müssen. Die verbleibenden werden vom Vungle Server zurückgegeben, wenn Sie sie in die Callback-URL miteinbeziehen.

Belohnungs-Konfiguration

Jetzt, da Sie den Nutzer für das Ansehen einer Werbung belohnen können, stellt sich die Frage, was Sie ihm geben. Wenn Sie Ihm jedes Mal Edelsteine geben, ist es recht einfach. Aber was, wenn Sie etwas fortgeschrittener vorgehen möchten? Wir verfügen nicht über eine eingebaute Option zur Belohnungskonfiguration, empfehlen aber Folgendes:

Beispiel: Coins gegenüber Leben

Angenommen Ihre App verfügt an mehreren Stellen über Werbungen mit Anreiz – sowohl in Ihrem Shop als auch bei jedem dritten "Game-Over". Sie möchten den Spieler mit Coins im Shop und mit einem Leben beim Game-Over belohnen. Konfigurieren Sie den Nutzer für jede Instanz von playAd() wie folgt:

userName123:coins or userName123:lives 

Wenn nun Ihre Server den Callback von Vungle erhalten, parsen Sie %user% für die richtige Belohnung!

Sicherheit

Authentifizierende Callbacks

Um zu bestätigen, dass die Callbacks, die Sie empfangen, von Vungle stammen, wählen Sie das Kontrollkästchen "Geheimer Schlüssel für sicheren Callback" für Ihre Anwendung an. Dies erzeugt einen geheimen Schlüssel wie diesen:

4YjaiIualvm8/4wkMBRH8pctlqB1NyzhK3qUGUar+Zc=

Sie können den Key wie folgt verwenden, um den Ursprung des Callbacks zu bestätigen:

  1. Erstellen Sie den "transaction verification"-String, indem Sie Ihren geheimen Schlüssel mit der Transaktions-ID mit einem Semikolon getrennt wie folgt verketten:
    transactionString = secretKey + ":" + %txid%
  2. Hashen Sie die Bytes des transactionString zweimal unter Verwendung des SHA-256-Algorithmus.

  3. Erzeugen Sie die den Token zur Transaktionsbestätigung, indem Sie die Ausgabe-Bytes von zwei Durchgängen des SHA-256-Hashens mit Hex kodieren, was in etwa so aussehen sollte:
    transactionToken = 870a0d13da1f1670b5ba35f27604018aeb804d5e6ac5c48194b2358e6748e2a8
  4. Überprüfen Sie, ob der von Ihnen generierte transactionToken demjenigen entspricht, der im Callback-Query-String als %digest% versendet wird.

Replay-Angriffe vermeiden

Um einen einzelnen Callback vor der wiederholten Rückgabe an Ihren Server zu hindern, speichern Sie die authentifizierten Transaktions-IDs und weisen künftige Callbacks mit doppelten Transaktions-IDs ab. Da eine Transaktions-ID einen Zeitstempel beinhaltet, können Sie die Anzahl an Transaktions-IDs begrenzen, die Sie speichern müssen, und auf Duplikate prüfen, indem Sie wie folgt eine Grenze für zeitnahe Callbacks erzwingen:

  1. Extrahieren Sie den Zeitstempel (in Millisekunden) der Transaktions-ID wie folgt:
    transactionMillis = transactionId.substringAfter(":")
  2. Überprüfen Sie, ob transactionMillis später als Ihre Grenze ist und ob transactionId seit Ihrer Grenze nicht gesehen wurde.

Beispielcode

Diese Abschnitte von Beispielcode sollen Sie bei der Sicherheits-Implementierung bei Server-zu-Server-Callbacks unterstützen. Wir bieten Beispiele für: Node.js, Java, Python und 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) {
  // Ist die Transaktion ordnungsgemäß formatiert?
  var tx_timestamp = getTransactionTimestamp(transaction_id);
  if (tx_timestamp === null) { return false; }

  // Befindet sich die Transaktion innerhalb einer ordnungsgemäßen Zeitspanne?
  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);
}

// Haben wir diese Transaktion zuvor gesehen?
// Hinweis: Für eine Erweiterung, die über nur diesen einen Node-Prozess hinausgeht, müssen Sie dies in einer
// Art zentralisiertem Datenspeicher hinterlegen. Ein Speicher, der atomische Insertionen in ein Set unterstützt
// sollte ausreichen. Die Redis-Datenbank ist mit Ihrem SADD-Befehl ein guter Anfang.
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) {
      // ungültiges Format der Transaktions-ID
    }
    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 falls transactionId neu ist, speichere transactionId mit ihren beigefügten 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);
      // ungültiger Zeitstempel
    }
  }

  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
Haben Sie Fragen? Anfrage einreichen

Kommentare