Button Webhooks

Creating a Webhook

Editing a Webhook

This will open the same Webhook dialogue as when you created it. Once you make your modifications, click Update to save your changes.

Pause or Resume a Webhook Endpoint

You can pause and resume a Webhook from sending data to the Webhook’s endpoint. Once in the Webhook view, select the Webhook you wish to pause/resume by clicking the checkbox to the left of the Webhook URL, then select Pause or Resume.

Automatically Paused Webhooks

A webhook endpoint is automatically paused when greater than 10% of webhooks created and attempted in the past hour have failed, and there is a minimum of five failed webhooks. We will send out an email notification to the organization indicating that the endpoint was paused. While the endpoint is paused, we will collect all new webhooks and redeliver them once the endpoint is resumed. You can resume a paused webhook through the Dashboard.

Webhook Specifications

Webhooks are sent over HTTP as a POST request to your configured URL. The body of the webhook request includes the following root-level fields.

Request Body:

Field Key Field Value / Purpose
request_id The unique identifier of a request attempt, even when retrying a failed request
data The order details represented as a Transaction containing order details
id The unique identifier for the webhook event
event_type The "state" the transaction event represents (see more about Event States below)

Below is an example request payload highlighting these root-level fields:

JSON

{
        "request_id": "attempt-XXX",
        "data": { ... },
        "id": "hook-XXX",
        "event_type": "tx-validated"
    }

The headers will include:

Header Key Header Value
Content-Type application/json
User-Agent Button Webhooks X.X.X
X-Button-Signature XXXXXXXXX

Webhook Event States

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

State Mutable? Billable? Definition
tx-pending Yes No Indicates that the transaction has been created but is not in a final state. It can still be adjusted or canceled. Commission is not yet guaranteed for a transaction in this state.
tx-validated No Yes Indicates that the finalization period for a transaction has concluded. The transaction will never be modified beyond this point. Commission as stated in the Webhook data.amount is final. All earnings are final and billable to the Brand.
tx-declined No No Indicates that the transaction has been cancelled by the user or the Brand. The transaction will never be modified beyond this point. The transaction will not be billable to the Brand.

When a transaction is reported with the tx-pending state, make sure to check whether it already exists in your system using the button_order_id. If it does, the webhook is reporting an order adjustment (i.e. the state of the data field has changed in some way).

Filter Out App Install Webhooks

Button’s webhooks fire requests on all commissionable events, including orders and app installations. So, when a commissionable event occurs, you will receive a webhook from Button.

If you’re using webhooks to signal when to commission the end user for making a purchase, be sure to filter out app-install from the data.category webhook request payload. Both the Play and App Stores prohibit rewarding users for app installs.

Transaction Details

A transaction is a record of a commission owed or earned.

A transaction is created when a user links from either a Publisher app or mobile website, to a Brand app or mobile website, and makes a purchase. Commissionable events include: downloading a Brand's App or making a purchase. We capture the transaction event types in the category field.

Transactions have an associated status that indicates a stage in the transaction life-cycle. A tx-validated status indicates that the transaction is immutable and billable. All pending status transactions are subject to change.

There are a number of financial fields within the transaction. Button models all fields that contain a financial value as an integer in the smallest currency unit (e.g. 100 for $1.00, -100 for $-1.00; 500 for £5.00, -500 for -£5.00, 100 for ¥100).

Transaction Properties

Transaction Key Transaction Value
account_id The publisher's Account ID for this transaction. A Publisher will have a separate Account ID for each currency in which they drive a Brand transaction.
advertising_id The device's advertising ID (IDFA on iOS and Google AID on Android). Only available when this value was reported.
amount The commission your organization is owed or has earned.
attribution_date The date Button used for selecting commission rates. ( ISO-8601).
btn_ref Button tracking token associated with this transaction.
button_id Legacy field; value is either static or unknown.
button_order_id Button-generated Order ID that is globally unique. Only available for new-user-order events.
campaign_id A Button configuration value distinguishing the Brand Campaign ID tied to this transaction.
category The type of event that generated this commission. Click here to navigate to those fields.
commerce_organization The Button organization ID for the Brand that received the billable transaction.
created_date When the transaction was first created in Button's system ( ISO-8601).
currency The currency of the commission, as a three-letter ISO currency code ( ISO 4217).
customer_order_id The customer-facing order ID reported by the Brand. Only available for new-user-order events and when reported by the Brand. Note: This field represents the order identifier communicated to the customer.
event_date When the transaction was processed by Button's system ( ISO-8601).
id Button generated Transaction ID that is globally unique and persists throughout the lifecycle of a transaction.
modified_date When the transaction was last modified ( ISO-8601).
order_currency The currency of the order. (only available for new-user-order events). ( ISO 4217).
* Note: Unless otherwise specified (by your Button representative), this will reflect the same value as currency (see above), and can be ignored in favor of that field. *
order_id Brand reported ID in their own namespace. Only available for new-user-order events and when reported by the Brand.
order_click_channel The channel in which the user completed the transaction. Values include: app, webview, unknown. Will be a string.
order_line_items An array of the order's line item details. Only available for new-user-order events.
order_purchase_date The Brand reported date and time that the user made the purchase. Only available when this value was reported. ( ISO-8601).
order_total The total value of the order. Includes total value of all items purchased, whether or not they qualified for a commission. Includes discounts, if applied. Only available for new-user-order events.
pub_ref The publisher reference value set when a Button or Link was fetched. Only visible to the Publisher, and only if it is configured. String with a maximum length of 512.
publisher_customer_id The publisher's internal customer ID. Only visible to the Publisher, and only if it is configured. String with a maximum length of 255.
publisher_organization The Button organization ID for the Publisher application that drove the transaction.
status The current state of the transaction, which will be one of the following values. More detail here.
validated_date When the transaction moved to validated status. ( ISO-8601).

Transaction Status Properties

Status Key Status Value
pending A transaction has been created, but it is subject to change. app-install events skip this state.
validated A transaction is final and immutable and will be paid out.
declined A transaction has been canceled (user or Brand cancels an entire order).

Transaction Order Line Item Properties

Order Line Item Key Order Line Item Value
identifier The unique identifier of your line item.
total The total price of all items bought in a particular line item (e.g. if 3 bananas were purchased for $3.00 each, total would be 900). If provided, amount will be ignored and set to round (total / quantity). Must be net of any discounts or taxes and represent the value a Brand expects to commission on.
amount The line item order value, in smallest decimal currency units. Will be an integer. Example: 1234 for $12.34, -1234 for -$12.34.
sku (optional): The Stock Keeping Unit of the line item. A Brand-specific identifier for the line item. String with a maximum length of 255.
quantity The number of individual units purchased. If present, will be an integer > 0.
publisher_commission The total commission generated by the line item. Will be an integer >= 0. A publisher_commission equal to 0 means no commission was earned for this line item. This field will only appear on transactions where created_date is after 2018-07-18T23:00:00Z.
gtin (optional): The Global Trade Item Number of the number. Supported values: UPC and EAN. String with 14 numeric digits.
category (optional): The topmost (or most general) level category of the line item. Example: For an item with category Electronics > Computers > Laptops this field would have the value Electronics. Will be a string.
subcategory1 (optional): The first subcategory of the line item. Example: For an item with category Electronics > Computers > Laptops this field would have the value Computers. Will be a string.
subcategory2 (optional): The second subcategory of the line item. Example: For an item with category Electronics > Computers > Laptops this field would have the value Laptops. Will be a string.
description Text describing the line item. Will be a string.
attributes A key/value store for strings. Defaults to an empty object. Only available when this value was reported.

Transaction Category Properties

Category Key Category Value
new-user-order Commission driven by an order event. No relation to user segment.
app-install Commission driven by the installation of the Brand application.

Example Webhook

JSON

{
      "id": "hook-xxxxxxxxxxxxxxxx",
      "event_type": "tx-validated",
      "data": {
        "posting_rule_id": null,
        "order_currency": "USD",
        "modified_date": "2020-04-04T00:00:00Z",
        "created_date": "2020-04-04T00:00:00Z",
        "order_line_items": [
          {
            "total": 1000,
            "identifier": "000000000000",
            "quantity": 1,
            "publisher_commission": 100,
            "subcategory1": "T-Shirts",
            "attributes": {},
            "amount": 1000,
            "description": "A very comfortable t-shirt",
            "sku": "000000000000",
            "category": "Clothes"
          }
        ],
        "button_id": "unknown",
        "campaign_id": null,
        "rate_card_id": "ratecard-xxxxxxxxxxxxxxxx",
        "order_id": null,
        "account_id": "acc-xxxxxxxxxxxxxxxx",
        "customer_order_id": null,
        "attribution_date": "2020-04-04T00:00:00Z",
        "btn_ref": "srctok-xxxxxxxxxxxxxxxx",
        "currency": "USD",
        "pub_ref": "publisher_pub_ref_123",
        "status": "validated",
        "event_date": "2020-04-04T00:00:00Z",
        "order_total": 1000,
        "advertising_id": null,
        "publisher_organization": "org-xxxxxxxxxxxxxxxx",
        "commerce_organization": "org-xxxxxxxxxxxxxxxx",
        "amount": 100,
        "button_order_id": "btnorder-xxxxxxxxxxxxxxxx",
        "publisher_customer_id": "publisher_user_id_123",
        "order_purchase_date": "2020-04-04T00:00:00Z",
        "id": "tx-xxxxxxxxxxxxxxxx",
        "order_click_channel": "app",
        "category": "new-user-order",
        "validated_date": "2020-04-04T00:00:00Z"
      },
      "request_id": "attempt-xxxxxxxxxxxxxxxxx"
    }

Receiver requirements

Your receiver must present a full chain of TLS certificates that ladder up to a well-known CA during the TLS handshake. Button's webhook dispatch systems do not support the Authority Information Access extension.

Beware that major web browsers do support Authority Information Access, which means that validating your recipient's TLS configuration via a web browser may give you false confidence. The openssl s_client is a useful utility for debugging these situations:

$ openssl s_client -servername yourhostname.com -connect yourhostname.com:443

If your certificate is valid without relying on Authority Information Access, you'll see Verify return code: 0 (ok) near the end of openssl's output.

Client Libraries

Button's client libraries include helper methods for validating incoming webhook requests:

Alternatively, you can integrate one of the following examples directly into your project.

Example Implementation

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

JavaScript

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');
    }

Ruby

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

Python

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 = b'YOUR_WEBHOOK_SECRET' # Do not publicly expose this

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

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

# 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.