Button Webhooks

Button Webhooks allow you to get notified when a transaction takes place based on traffic you drive to brands from your app. In this section, we'll dive into Button Webhooks and close the loop on the user's e-commerce journey.

Creating a Webhook

  • Creating a Button Webhook completed within the Button Dashboard.
  • Log in using your Organization’s Button credentials, then click on your Organization’s drop-down icon in the top right to navigate menu options. Select Webhooks.
  • This will navigate you to the Webhooks overview page where you can see your existing Webhooks if any have been created. To create a Webhook, select Add a Webhook on the right side of this view.
  • A modal will pop up and ask you for some details before creating the Webhook. This includes your organization’s URL where you wish to receive Webhook data, along with the type of data you wish to receive. Check the three Transaction event types – Validated, Pending, and Declined. Then select Add.

Editing a Webhook

  • After you’ve created a Button Webhook, you can edit it from within the Webhooks page in your Button Dashboard.
  • Log in using your Organization’s Button credentials, then click on your Organization’s drop-down icon in the top right to navigate menu options. Select Webhooks.
  • This will navigate you to the Webhooks overview page where you can see your existing Webhooks if any have been created. To edit a Webhook, select an existing one by clicking on the Webhook’s URL. Then, within that Webhook view, click Edit in the top right of the screen.

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 KeyField Value / Purpose
request_idThe unique identifier of a request attempt, even when retrying a failed request
dataThe order details represented as a Transaction containing order details
idThe unique identifier for the webhook event
event_typeThe "state" the transaction event represents (see more about Event States below)

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

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

The headers will include:

Header KeyHeader Value
Content-Typeapplication/json
User-AgentButton Webhooks X.X.X
X-Button-SignatureXXXXXXXXX

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:

StateMutable?Billable?Definition
tx-pendingYesNoIndicates 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-validatedNoYesIndicates 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 guaranteed. All earnings are final and billable to the Brand.
tx-declinedNoNoIndicates 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 KeyTransaction Value
account_idThe 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_idThe device's advertising ID (IDFA on iOS and Google AID on Android). Only available when this value was reported.
amountThe commission your organization is owed or has earned.
attribution_dateThe date Button used for selecting commission rates. (ISO-8601).
btn_refButton tracking token associated with this transaction.
button_idLegacy field; value is either static or unknown.
button_order_idButton-generated Order ID that is globally unique. Only available for new-user-order events.
campaign_idA Button configuration value distinguishing the Brand Campaign ID tied to this transaction.
categoryThe type of event that generated this commission. Click here to navigate to those fields.
commerce_organizationThe Button organization ID for the Brand that received the billable transaction.
created_dateWhen the transaction was first created in Button's system (ISO-8601).
currencyThe currency of the commission, as a three-letter ISO currency code (ISO 4217).
customer_order_idThe 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_dateWhen the transaction was processed by Button's system (ISO-8601).
idButton generated Transaction ID that is globally unique and persists throughout the lifecycle of a transaction.
modified_dateWhen the transaction was last modified (ISO-8601).
order_currencyThe 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_idBrand reported ID in their own namespace. Only available for new-user-order events and when reported by the Brand.
order_click_channelThe channel in which the user completed the transaction. Values include: app, webview, unknown. Will be a string.
order_line_itemsAn array of the order's line item details. Only available for new-user-order events.
order_purchase_dateThe Brand reported date and time that the user made the purchase. Only available when this value was reported. (ISO-8601).
order_totalThe 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_refThe 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_idThe publisher's internal customer ID. Only visible to the Publisher, and only if it is configured. String with a maximum length of 255.
publisher_organizationThe Button organization ID for the Publisher application that drove the transaction.
statusThe current state of the transaction, which will be one of the following values. More detail here.
validated_dateWhen the transaction moved to validated status. (ISO-8601).

Transaction Status Properties

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

🚧

Note

Button may add additional fields at any time. As such, ensure that you do not have validations in place that check for the full schema of the webhook; only put validations/logic place on specific fields.

Transaction Order Line Item Properties

Order Line Item KeyOrder Line Item Value
identifierThe unique identifier of your line item.
totalThe 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.
amountThe 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.
quantityThe number of individual units purchased. If present, will be an integer > 0.
publisher_commissionThe 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.
descriptionText describing the line item. Will be a string.
attributesA key/value store for strings. Defaults to an empty object. Only available when this value was reported.

Transaction Category Properties

Category KeyCategory Value
new-user-orderCommission driven by an order event. No relation to user segment.
app-installCommission driven by the installation of the Brand application.

Example Webhook

{
      "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"
    }

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:

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


What’s Next