Button Publisher Integration Guide on

Button Publisher Integration Guide

This guide walks you through how to add Button to your iOS application. In this guide, we'll:

  1. Add the Button SDK
  2. Configure User Attribution
  3. Set App Permissions
  4. Create a Button Purchase Path
  5. Get Notified on User Activity via Button Webhooks to Reward Users (Loyalty Only)

Add the Button SDK

Add the Button SDK through CocoaPods.

pod "Button", "~>6"

In your AppDelegate file, configure the Button SDK in the didFinishLaunchingWithOptions method.

import Button

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

  // Debugging enabled (do not include in production)
  Button.setDebugLoggingEnabled(true)

  // Replace app-xxxxxxxxxxxxxxxx with your App ID from the Button Dashboard https://app.usebutton.com
  Button.configure(applicationId: "<#app-xxxxxxxxxxxxxxxx#>", completion: { error in
    // Optional callback to inspect whether an error occurred while
    // creating or resuming a session. See also debug logging.
  }

  return true
}
@import Button;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  // Debugging enabled (do not include in production)
  [Button setDebugLoggingEnabled:YES];

  // Replace app-xxxxxxxxxxxxxxxx with your App ID from the Button Dashboard https://app.usebutton.com
  [Button configureWithApplicationId:@"<#app-xxxxxxxxxxxxxxxx#>" completion:^(NSError *error) {
    // Optional callback to inspect whether an error occurred while
    // creating or resuming a session. See also debug logging.
  }];

  return YES;
}

Configure User Attribution

Note: Passing a User ID is required for Loyalty Publishers.

Passing a user ID ensures subsequent Merchant activity (such as installs or purchases) is attributed to a user. This can either be your user ID, email or a stable hash of one. You can use this later to look up orders, activity and identify the user in Webhooks.

Set the user ID in your login handler.

Button.user.setIdentifer("<#YOUR_LOGGED_IN_USERS_ID#>")
[Button.user setIdentifier:@"<#YOUR_LOGGED_IN_USERS_ID#>"];

If your app offers logout functionality, invoke the SDK's logout feature during your logout handler.

Button.clearAllData();
[Button clearAllData];

Set App Permissions

Open your Project's Info.plist file, add the LSApplicationQueriesSchemes key, and add the scheme of all Merchant apps that you launch partnerships with.

Update Build Settings

Create a Button Purchase Path

// Step 1 - Create a Purchase Path request
let url = URL(string: "https://www.example.com")
let request = PurchasePathRequest(url: url)

// Optionally associate a unique token (e.g. campaign Id)
// request.pubRef = "abc123"

// Step 2 - Fetch a Purchase Path object
Button.purchasePath.fetch(request: request) { purchasePath, error in

    // Step 3 - Start Purchase Path flow
    purchasePath?.start()
}
// Step 1 - Create a Purchase Path request
NSURL *url = [NSURL URLWithString:@"https://www.example.com"];
BTNPurchasePathRequest *request = [BTNPurchasePathRequest requestWithURL:url];

// Optionally associate a unique token (e.g. campaign Id)
// request.pubRef = @"abc-123";

// Step 2 - Fetch a Purchase Path object
[Button.purchasePath fetchWithRequest:request purchasePathHandler:
^(BTNPurchasePath *purchasePath, NSError *error) {

    // Step 3 - Start Purchase Path flow
    [purchasePath start];
}

If Button can exchange the the given url for a fully attributed action, the fetch will complete with a PurchasePath. Starting a purchasePath will pass control to the Button SDK which will open the merchant app, install flow, or web checkout.

You can pass an optional pubRef (typically used for click IDs, campaign IDs, etc.), which will be passed back to you via webhooks.


Get Notified on User Activity via Button Webhooks to Reward Users (Loyalty Only)

In order to reward your users for the Merchant activity such as purchases, Button will need a service endpoint to send requests to. You can configure this webhook - and test it - in your Button Dashboard.

You can enable webhook requests for three states of a transaction:

  • validated - A transaction is finalized according to the merchant.
  • pending - A transaction is not yet final, e.g. the merchant has a 30-day finalization window
  • declined - A transaction that was in a pending state was cancelled or refunded.

Example Implementation

For generic use in the popular web frameworks Express, Sinatra, and Flask for Node.js, Ruby, and Python, respectively:

var express = require('express');
var app = express();

var bodyParser = require('body-parser');
var crypto = require('crypto');

var port = process.env.PORT || 5000;

// You can find your Webhook's Secret in the Dashboard at https://app.usebutton.com/webhooks on the page for a specific Webhook.
var WEBHOOK_SECRET = 'YOUR_WEBHOOK_SECRET' // Do not publicly expose this

app.use(bodyParser.json({ verify: verify, type: 'application/json' }));

app.post('/webhook', function(req, res) {
  // Here is the webhook data
  var data = req.body.data;

  res.sendStatus(200);
})

app.listen(port, function() {
  console.log('Listening on port: ' + port)
})

function verify(req, res, buf, encoding) {
  if (req.headers['x-button-signature'] !== signature(buf)) {
    throw new Error('Invalid Webhook Signature');
  }
}

function signature(requestBody) {
  return crypto.createHmac('sha256', WEBHOOK_SECRET)
    .update(requestBody)
    .digest('hex');
}
require 'sinatra'

require 'openssl'
require 'json'

# You can find your Webhook's Secret in the Dashboard at https://app.usebutton.com/webhooks on the page for a specific Webhook.
WEBHOOK_SECRET = 'YOUR_WEBHOOK_SECRET' # Do not publicly expose this

post '/webhook' do
  request_body = request.body.read
  raise 'Invalid Webhook Signature' if request.env["HTTP_X_BUTTON_SIGNATURE"] != signature(request_body)

  json = JSON.parse(request_body)

  # Here is the webhook data
  data = json['data']

  status 200
end

def signature request_body
  OpenSSL::HMAC.hexdigest(
    OpenSSL::Digest.new('sha256'),
    WEBHOOK_SECRET,
    request_body
  )
end

import os
import hmac
import hashlib

from flask import Flask, request, abort

app = Flask(__name__)

# You can find your Webhook's Secret in the Dashboard at https://app.usebutton.com/webhooks on the page for a specific Webhook.
WEBHOOK_SECRET = 'YOUR_WEBHOOK_SECRET' # Do not publicly expose this

@app.route('/webhook', methods=['POST'])
def webhook():
    computed_signature = signature(request.data)
    sent_signature = request.headers.get('X-Button-Signature').encode('utf8')

    if not hmac.compare_digest(computed_signature, sent_signature):
        abort(403)

    # Here is the webhook data
    data = request.json['data']

    return 'ok', 200

def signature(request_body):
    return hmac.new(WEBHOOK_SECRET, request_body, hashlib.sha256).hexdigest()

if __name__ == "__main__":
    app.run()

The full source code for each of these sample apps can be found here.

Event States

There are 3 unique states to orders in Button's systems, presented in the event_type field in the webhook's root. The below table provides details on each state:

State Terminal? Mutable? Billable? Definition
tx-pending No Yes No Indicates that the transaction has been created, but not yet entered a final state, meaning that it can still be cancelled or adjusted through Button's API.
tx-validated Yes No Yes Indicates that the finalization period for a transaction has concluded successfully, and thus will be billable to the Merchant.
tx-declined Yes No No Indicates that the transaction has been cancelled by the user, and thus will not be billable to the Merchant.

Validate the Request Source

Button cryptographically hashes the payload we send with a Webhook Secret. Each Webhook configured in the Dashboard will have its own secret. Then, on the receiving end, the server may compute the same hash given a copy of the Secret and the request payload. Equating this computed hash with the one Button sent guarantees a few things (assuming your Secret has never been compromised):

  1. The request is guaranteed to be from Button
  2. The contents of the request are guaranteed to be exactly as Button sent them (that is, no malicious computer could have intercepted the request and altered the contents of the payload)

To generate an equivalent hash, you must:

  1. Capture the raw request body before any parsing has occurred. This should be a UTF-8 encoded string.
  2. Supply your Webhook Secret and the string from (1) to your language's HMAC implementation, using SHA-256 as the hashing algorithm.
  3. Digest the result from (2) as a hex-encoded string.

Note: It's a good idea to read your Webhook Secret from the server's environment rather than checking it into version control.

Filter for "Purchase Events"

Button webhooks will send all "commissionable" events (e.g. installs, purchases). You only want to reward on purchase requests, so make sure you check the category field, and note that app installs will come through with the category field as app-install.

Validate Request Data Needed for User Rewards

The data we get from merchants can often change as we work with them to optimize our integrations. For this reason, make sure you check for the fields that you need to reward vs. the overall request payload structure.

Acknowledgment

Make sure you let our webhook service know that you received the request by sending a 2XX HTTP response code.

Error Handling

Button’s webhooks enforce a sequential delivery of commissionable events. This means that before we move-on & report the "next" event that occurred, we require confirmation that the "current" webhook request was properly accepted. For this reason, it’s important to respond to a webhook request with a 2XX HTTP response code, unless a real error occurred requiring subsequent webhooks to be paused until resolution of the error.

Below is a list of suggested response codes, and what they signify to Button:

  • 200 (or 204) - Webhook accepted & successfully processed
  • 202 - Webhook accepted, but processing was unsuccessful. We recommend logging these & flagging them up with Button’s Customer Experience team for further investigation
  • Non 2XX (e.g. 500/400/etc) - Webhook not accepted or processed due to an error. Retry this webhook with an exponential back-off strategy for the first hour. After that retries will happen once an hour for up to 3 days.

Notable Distinctions

  • All amounts come through in cents (no decimals). (e.g. a $10.00 order would be "order_total":"1000")
  • order_total is the total order value, amount is the commission amount (e.g. if the commission is 10%, a $10 order would generate a $1 commission - "order_total":"1000" with "amount":"100")