ayeT-Studios Publisher Android SDK Integration Guide (v3.6)



Updates

2022-04-12: v3.6 - Updated SDK to Android Api 30 (Android 11) and restored compliance with updated privacy requirements
2022-02-11: v3.5.2 - Removed Rewarded Video from documentation
2020-08-11: v3.5.1 - Updated documentation to introduce new parametars to differentiate chargebacks from conversions
2020-07-29: v3.5 - Improved UI / handling, removed deprecated INSTALL_REFERRER, performance improvements and bugfixes
2019-08-20: v3.4 - Updated Android SDK to support decimal places in payouts (currency amount) for native offers
2019-07-19: v3.3 - Updated Android SDK and documentation to reflect the adslot changes
2019-06-21: v3.2 - Native Offer Feed functionality
2019-02-01: v3.1 - Fixed conversion tracking issues under certain conditions (application lifecycle monitoring improved)
2018-11-29: v3.0 - Full support for API 26+ build targets, bugfixes, simplified use & improved handling under poor network conditions
2018-07-02: v2.1 - Fixed video ad orientation & resolution issues on Android 8.1
2018-06-18: v2.0 - Video Ads & Rewarded Video Ads
2017-11-01: v1.1 - Fixed incompatibilities with present GSON dependencies
2017-10-29: v1.0 - Initial Release of our Publisher SDK (Android)


Quick 3.1 To 3.6 Migration Guide

- Update your AyetSdk.showOfferwall() calls to pass the target offerwall adslot name as second parameter

- Update your OfferwallActivity declaration in AndroidManifest.xml according to the documentation


Quick 2.1 To 3.1 Migration Guide

- remove the PACKAGE_ADDED BroadcastReceiver from AndroidManifest.xml
- call AyetSdk.init(...) with "getApplication()" instead of "getApplicationContext()"


Quick 1.1 To 2.1 (Video Ads) Migration Guide

- add video activity to AndroidManifest.xml (see topic 3)
- update your proguard-rules file (see topic 9)


Introduction

The ayeT-Studios Publisher SDK allows you to easily monetize your app and reward your users with in-app currency. Your users get access to our offerwalls, allowing them to earn currency for completing tasks such as trying new apps, answering surveys, signing up for free trials or watching video ads. The integration is simple, allows both managed (our servers store user information and balances) and unmanaged (you receive callbacks and handle user wallets yourself) currencies and also works well alongside traditional in-app purchases.




Topics

  1. Prerequisites
  2. Download the library
  3. Add the library to your Android Studio project
  4. Initialize the SDK & Receive Managed Balances
  5. Show the Offerwall
  6. Request Native Offer Feed
  7. Show Video Ads
  8. Appendix I: Unmanaged Currency Handling / Conversion Callbacks
  9. Appendix II: Proguard Rules / Release Builds
  10. Appendix III: Multiple INSTALL_REFERRER Receivers





1. Prerequisites


Before integrating the SDK in your app, you should sign up for a publisher account. Afterwards login as publisher and create a new Android App Placement using the correct package name you intend to use for your final app.
This is important since we'll generate an APP_KEY / Identifier for you which has to be added to your AndroidManifest (see step 3).

2. Download the library


You can download the lastest version of our publisher library here:
ayetpublisher3.6.jar

3. Add the library to your Android Studio project


Copy the downloaded jar library to your Android Studio project (in the app/libs/ folder).
Go to "Module Settings" (F12 or right-click your app in the Project View) and check the dependencies tab to make sure the library is added as a file dependency and set to "Compile":




Afterwards open your AndroidManifest.xml and add our offerwall activity to your application scope:

<activity
    android:name="com.ayetstudios.publishersdk.OfferwallActivity"
    android:configChanges="orientation|screenSize">
    <intent-filter android:label="offer">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="offer" android:host="com.example.myapplication" /> <!-- Replace with your lower case package name -->
    </intent-filter>
</activity>
<activity android:name="com.ayetstudios.publishersdk.VideoActivity" android:configChanges="orientation|screenSize" />	                


Another AndroidManifest.xml requirement is your AYET_APP_KEY which you can fetch from our publisher dashboard in your placement or adslot details - add it to the application scope as well:

<meta-data android:name="AYET_APP_KEY" android:value="xxxxxxxxxxxxxxxx" />	                


Also make sure to check your permissions in the AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />  <!-- mandatory permission -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />  <!-- optional -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- optional -->	                


This is an example for a complete AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication"> <!-- Replace with your lower case package name -->

    <uses-permission android:name="android.permission.INTERNET" />  <!-- mandatory permission -->

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />  <!-- optional -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- optional -->

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

	<!-- Publisher SDK Specific-->
        <activity
            android:name="com.ayetstudios.publishersdk.OfferwallActivity"
            android:configChanges="orientation|screenSize">
            <intent-filter android:label="offer">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="offer" android:host="com.example.myapplication" /> <!-- Replace with your lower case package name -->
            </intent-filter>
        </activity>
        <activity android:name="com.ayetstudios.publishersdk.VideoActivity" android:configChanges="orientation|screenSize"></activity>

        <meta-data android:name="AYET_APP_KEY" android:value="xxxxxxxxxxxxxxxx" />
        <!-- End of: Publisher SDK Specific-->


    </application>

</manifest>	                

4. Initialize the SDK & Managed User Balances


In this step, we are going to initialize the SDK in the main activity. We also use callbacks to track the user's account balance - this is optional and not required if you're planning to manage user balances on your own servers.
Attention: The username or external identifier passed in the init call (e.g. username, hashed email address, etc.) will be accessible in the conversion callbacks through the {external_identifier} parameter.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    AyetSdk.init(getApplication(), "username (external identifier)", new UserBalanceCallback() { // UserBalanceCallback is optional if you want to manage balances on your servers
            @Override
            public void userBalanceChanged(SdkUserBalance sdkUserBalance) {
                Log.d("AyetSdk" , "userBalanceChanged - available balance: "+sdkUserBalance.getAvailableBalance()); // this is the new total available balance for the user
            }

            @Override
            public void userBalanceInitialized(SdkUserBalance sdkUserBalance) {
                Log.d("AyetSdk" , "SDK initialization successful");
                Log.d("AyetSdk" , "userBalanceInitialized - available balance: "+sdkUserBalance.getAvailableBalance()); // this is the total available balance for the user
                Log.d("AyetSdk" , "userBalanceInitialized - spent balance: "+sdkUserBalance.getSpentBalance()); // this is the total amount spent with "AyetSdk.deductUserBalance(..)"
                Log.d("AyetSdk" , "userBalanceInitialized - pending balance: "+sdkUserBalance.getPendingBalance()); // this is the amount currently pending for conversion (e.g. user still has offer requirements to meet)
            }

            @Override
            public void initializationFailed() {
                Log.d("AyetSdk", "initializationFailed - please check APP API KEY & internet connectivity");
            }
        });

    setContentView(R.layout.activity_main);
}	                


If you want to make sure the SDK has been initialized and is ready to display the offerwall or video ads, you can use the following function:
if (AyetSdk.isInitialized()) {
    Log.d("AyetSdk" , "SDK is ready");
} else {
    Log.d("AyetSdk" , "SDK is NOT ready");
}	                


If you want to spend user currency, for example if the user clicks a "purchaseInAppItem" button, you can utilize the deductUserBalance function:

mPurchaseInAppItemButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        int amount=100;
        AyetSdk.deductUserBalance(getApplication(), amount, new DeductUserBalanceCallback() {
            @Override
            public void success() {
                Log.d("AyetSdk" , "deductUserBalance - successful, new available balance: "+AyetSdk.getAvailableBalance());
                // TODO: activate the purchased content
            }

            @Override
            public void failed() {
                Log.d("AyetSdk" , "deductUserBalance - failed");
                // this usually means that the user does not have sufficient balance in his account
            }
        });
    }
});	                

5. Show the Offerwall


Showing the offerwall for an offerwall adslot is straight-forward, you can simply call showOfferwall from any Activity:

AyetSdk.showOfferwall(getApplication(), "offerwall adslot name");	                
showOfferwall starts the offerwall activity for the adslot you pass as second parameter and displays available tasks for the user to complete.

6. Request Native Offer Feed


If you want to display the offers manually in a custom form (e.g. "native offers"), you can call getNativeOffers for a Native Offer Feed adslot you created in your placement:

AyetSdk.getNativeOffers(getApplication(), "native adslot name", new NativeOffersCallback() {
                    @Override
                    public void onResult(boolean success, NativeOfferList responseMessage) {
                        if (success) {
                            Log.e("JSON Native Offers",  new Gson().toJson( responseMessage.offers ) );
                            MyApplication.this.nativeOfferCache=responseMessage.offers;
                        }
                    }
                });	                
getNativeOffers fetches all offers fitting this user / device in the background and calls NativeOffersCallback on completion. This usually takes between 0.5s and 4s depending on your adslot configuration and traffic sources.


After retrieving a list of native offers, you can store them and later activate one of the offers like this:

AyetSdk.activateOffer(MainActivity.this, MyApplication.nativeOfferCache.get(0).getId(), new ActivateOfferCallback() {
                        @Override
                        public void onFailed() {
                            // This is triggered if the offer is not available anymore or if connectivity problems are present
                        }

                        @Override
                        public void onSuccess() {
                            // This is triggered right before starting a Google Play intent or trying to open the default browser
                        }
                    });	                
activateOffer takes an offer id found in the native offer feed, checks the requirements and tries to reserve it for the user.


Class members for each AyetOffer entry are:
String id; // the id of the offer, used in activateOffer call
String name; // display name of the offer
String icon; // url to the offer icon
int category; // NativeOfferList.CATEGORY_INCENT | NativeOfferList.CATEGORY_NONINCENT
int type; // NativeOfferList.TYPE_CPI | NativeOfferList.TYPE_CPA | NativeOfferList.TYPE_CPL
String description; // a short description of the offer
String instructions; // conversion instructions for the user
int conversionTime; // average / estimated conversion time in seconds
int payout; // the payout in the configured virtual currency
ArrayList<AyetCreative> creatives; // optional: additional creative data (images, videos) for the offer	                

7. Show Video Ads


Introduction to video ads

Video ads are a great way to monetize your apps & games. Compared to banner or static interstitial ads, they deliver an outstanding eCPM and are widely accepted by users - if implemented correctly.
While video ads are non incentivized offers which either pay the publisher per view (CPM) or per installation / action (CPI / CPA), we give publishers the opportunity to reward their users for watching an optional (skippable) "rewarded" video ad.
Regardless of the video ad type, it's strictly forbidden to prompt users to install apps or interact with the promoted assets.

Normal video ads work like TV commercials. They are between 15s and 30s long and cannot be interrupted. A typical use case is a commercial break in games between levels.
Rewarded video ads are optional and user initiated. Users can skip rewarded ads at any point, but will be rewarded with in-app items or virtual currency if they watch it completely.

Normal videos are not limited in terms of frequency and capping, but for rewarded video ads it's strongly advised to check and configure the placement correctly (Settings > Rewarded Video Ads > Rewarded Video Capping & Frequency) to prevent abuse.

Both video ads and rewarded video ads send publisher earning callbacks without user or device information (so only offer information and payout_usd is set) if your callback URL is configured in the placement overview.
Please note that for CPM offers, it's possible that multiple views for a campaign are grouped into one earning and callback to avoid congestion.

Rewarded video ads can additionally (and independently from publisher earning callbacks) send user currency callbacks, if Settings > Rewarded Video Ads > Enable S2S View Callbacks is enabled. Given that the placement callback url is set and the currency amount per rewarded video view is greater than 0, this will send a S2S callback for each completed rewarded video view (currency_amount is set, but payout_usd is always 0).
Before starting to implement video ads, please make sure that this ad format has been enabled for your placement.
If not, go to Placement > Settings > Video Ads and request access or contact your account manager to have them enabled.


To show a non-interruptable, fullscreen video ad (15-30 seconds depending on your placement video settings), you can make the following showVideoAd call:
AyetSdk.showVideoAd(getApplication(), "video adslot name", AyetSdk.FLAG_DEFAULT, new VideoCallbackHandler() {
        @Override
        public void nofill() {
            // This is called when either no video is available to watch, the AyetSdk has not been initialized or there were network/connectivity issues
            Log.d(TAG , "AyetSdk.showVideoAd::nofill()");
        }
        @Override
        public void finished() {
            // This is called when the video activity is done (video completed, aborted or other problems like network speed)
            Log.d(TAG , "AyetSdk.showVideoAd::finished()");
        }
        @Override
        public void willBeShown() {
            // This is called after selecting a video and right before the video activity is started to play it
            Log.d(TAG , "AyetSdk.showVideoAd::willBeShown()");
        }
    });	                

If you want to restrict video ads to wifi connectivity only, you can append the AyetSdk.FLAG_WIFI_ONLY flag:
AyetSdk.showVideoAd(getApplication(), "video adslot name", AyetSdk.FLAG_DEFAULT | AyetSdk.FLAG_WIFI_ONLY, new VideoCallbackHandler() {...	                

By default, videos will be shown in their optimized orientation (which is landscape in most cases). If your app should strictly stay in a fixed orientation, you can utilize the AyetSdk.FLAG_ORIENTATION_PORTRAIT or AyetSdk.FLAG_ORIENTATION_LANDSCAPE flags to enforce the video orientation:
AyetSdk.showVideoAd(getApplication(), "video adslot name", AyetSdk.FLAG_DEFAULT | AyetSdk.FLAG_ORIENTATION_PORTRAIT, new VideoCallbackHandler() {...	                


Another capability of the SDK is to asychronously request a video ad. This gives you the opportunity to decide whether the video should actually play or if it should be discarded.
Hint: The average request-to-play time is around 1.5-3 seconds for video ads under good network conditions.
To request a video ad asynchonously, you can use the following code (note the different callback interface):
AyetSdk.showVideoAd(MainActivity.this, "video adslot name", AyetSdk.FLAG_DEFAULT | AyetSdk.FLAG_ASYNC, new VideoAsyncCallbackHandler() {
        @Override
        public void nofill() {
            // This is called when either no video is available to watch, the AyetSdk has not been initialized or there were network/connectivity issues
            Log.d(TAG , "AyetSdk.showVideoAdAsync::nofill()");
        }
        @Override
        public void finished() {
            // This is called when the video activity is done (video completed, aborted or other problems like network speed)
            Log.d(TAG , "AyetSdk.showVideoAdAsync::finished()");
        }
        @Override
        public void willBeShown() {
            // This is called after selecting a video, after "videoAd.showVideo()" has been called in "ready(...)" and right before the video activity is started to play it
            Log.d(TAG , "AyetSdk.showVideoAdAsync::willBeShown()");
        }
        @Override
        public void ready(final VideoAdInterstitial videoAd) {
            // This is called when a video has been selected and is ready to be shown. You have about 60 seconds to decide whether you want to play the video by calling "videoAd.showVideo();"
            // If showVideo isn't called, the video will be discarded and the video activity won't start.

            Log.d(TAG , "AyetSdk.showVideoAdAsync::ready()");

			// As an example, we're starting a timer task here who is signaling the SDK to show the prefetched video after a delay of 4 seconds
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    videoAd.showVideo();
                }
            } , 4000);
        }
    });
                


8. Appendix I: Unmanaged Currency Handling / Conversion Callbacks


If you want to manually manage your users currencies on your own servers, you can configure a conversion callback url in our publisher dashboard.
To do so, navigate to Placements / Apps, edit your app placement and set the Callback Url to your server's postback url:


If this is the callback url your set for your app placement:
https://your-server.com/callback?network=ayetstudios&amount={currency_amount}&uid={external_identifier}&device={advertising_id}&payout_usd={payout_usd}

A typical conversion callback sent by our server will look like this:
https://your-server.com/callback?network=ayetstudios&amount=360&uid=username&device=[GAID]&payout_usd=0.36
* Note: This assumes you set the user identifier to username in your AyetSdk.init(..) call, the currency conversion rate in your placement was 1000 per $1 and the user completed an offer with a $0.36 payout.

Important: Your server must always reply with an HTTP 200 status code to our postbacks. Otherwise we will resend the postback 12 times over a span of one hour before giving up.

Available Macros for Postback URLs:
{transaction_id}stringUnique transaction id - use for duplicate checks. If chargeback it's prepend with r-
{payout_usd}floatThe actual conversion payout in USD. If chargeback value is negative.
{currency_amount}floatThe amount of currency the user earned (taken from your offerwall currency configuration). If chargeback value is negative.
{external_identifier}stringThe user identifier set in your app for this user when initializing the sdk
{user_id}integerOur internal id for this offerwall user
{placement_identifier}stringThe placement_identifier for which the conversion occured
{adslot_id}intThe id of the adslot for which the conversion occured
{ip}stringConverting device's IP address if known, 0.0.0.0 otherwise
{offer_id}intOffer ID of the converting offer
{offer_name}stringName / title of the converting offer
{device_uuid}stringayeT-Studios internal device identificator
{device_make}stringDevice manufacturer
{device_model}stringDevice model
{advertising_id}stringDevice advertising id (GAID/IDFA) if known, otherwise empty
{sha1_android_id}stringDevice sha1 hashed android id if known, otherwise empty
{sha1_imei}stringDevice sha1 hashed imei if known, otherwise empty
{is_chargeback}intEither 0 or 1. Indicator if the callback is a conversion (0) or a chargeback (1).
{chargeback_reason}stringReason why chargeback created. Only available if is_chargeback set to 1.
{chargeback_date}stringDate of chargeback creation. Only available if is_chargeback set to 1.
{task_name}stringOnly available for cpe campaigns, shows individual task name for that conversion.
{currency_identifier}stringShows virtual currency name as set in adslot.
{currency_conversion_rate}decimalShows currency conversion rate used to calculate user currency for the given conversion.


Postback Verification with HMAC Security Hash (optional):
Our server will always add a custom header, X-Ayetstudios-Security-Hash, containing a SHA256 HMAC hash of the request parameters and your publisher api key.
Your API key can be found in your dashboard at ayetstudios.com under settings.

To verify the hash, perform the following steps:
(1) Get all request parameters
(2) Order the request parameters alphabetically
(3) Build and compare the HMAC hash using the ordered request parameter string and your API key

PHP Example:
ksort($_REQUEST, SORT_STRING);
$sortedQueryString = http_build_query($_REQUEST, '', '&'); // "adslot_id=123&currency_amount=100&payout_usd=1.5...."
$securityHash = hash_hmac('sha256', $sortedQueryString, 'YOUR PUBLISHER API KEY');
if($_SERVER['HTTP_X_AYETSTUDIOS_SECURITY_HASH']===$securityHash) { // actually sent as X-Ayetstudios-Security-Hash but converted by apache2 in this example
    // success
}
else {
    // invalid signature
}                    


If your want to restrict postbacks to our callback server IPs, please whitelist the following IPs and check back regularly for possible changes:
35.165.166.40
35.166.159.131
52.40.3.140	    
Last IP List Update: 2017-04-07


9. Appendix II: Proguard Rules / Release Builds


If you're going to use Proguard in your release build to obfuscate your application, make sure to add the following rules to your proguard-rules.pro files:
-keep class com.ayetstudios.publishersdk.messages.** {*;}
-keep public class com.ayetstudios.publishersdk.AyetSdk
-keepclassmembers class com.ayetstudios.publishersdk.AyetSdk {
   public *;
}
-keep public interface com.ayetstudios.publishersdk.interfaces.UserBalanceCallback { *; }
-keep public interface com.ayetstudios.publishersdk.interfaces.DeductUserBalanceCallback { *; }

-keep class com.ayetstudios.publishersdk.models.VastTagReqData { *; }
	                

Important: It's always highly recommended to test your release builds before publishing them on Google Play to verify that they behave as intended!