Configure Webhooks

Configure Webhooks

Button allows you to configure webhooks so you can get real-time updates to your system when users push Buttons in your app. To do so, you will need to configure the destination of the webhook and what events you want to receive. Then, Button will send you an HTTP request when any of those events are created.

It's a very good idea to not inherently trust any request received by your webhook endpoint. See Security for details.

For full source code of sample apps in Node.js, Ruby, and Python that securely receive Button webhooks, see here. You can also find these in the Example Implementation section, below.

Create or Edit a Webhook

To create a new webhook or edit an existing one, you can do so on the Dashboard.


Webhooks will be sent with an HTTP POST to your configured URL.

The body of the message:

    "id": "hook-1c1d7937f9715e3e",
    "event_type": "tx-validated",
    "data": {},
    "request_id": "attempt-4843253923fd59a8",
  • id: the unique ID for the webhook
  • event_type: the type of event that triggered the webhook. This field also indicates what object will be in the data field. List of event states.
  • data: this is the main payload of the message. Description of the different event objects.
  • request_id: since messages can be sent multiple times, this is the unique ID per request

The headers will include

  • Content-Type: application/json
  • User-Agent: Button Webhooks X.X.X
  • X-Button-Signature: XXXXXXXXX

Event States

  • tx-pending: A transaction has entered the pending state. Object transaction.
  • tx-validated: A transaction has entered the validated state. Object transaction.
  • tx-declined: A transaction has entered the declined state. Object transaction.

Event Objects


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

A transaction is created when a user moves from one app to another and takes an action like installing the app, or purchasing an item (API details for a Commerce app to report an order). This is so that we can credit the Publisher app for driving the user, and debit the Commerce app as they received something they wanted (a new user or an order). Since there various events that can drive a transaction, we capture that in the category field. Also, transactions have a status associated with them, to allow you to identify the current state the of the life-cycle the transaction is in.

  • amount: The amount your organization is owed or earned, in the smallest decimal currency units.
  • currency: The currency of the amount, as a three-letter ISO currency code (ISO 4217).
  • category: The type of event that has lead to the transactions.
    • new-user-order: An order for a user who just installed the Merchant application.
    • existing-user-order: An order for a user who already had the Merchant application installed.
    • app-install: An installation of the Merchant application.
  • created_date: When the transaction was first created.
  • modified_date: When the transaction was last modified.
  • validated_date (optional): When the transaction moved to validated status.
  • event_date: When the order or app install occurred.
  • btn_ref: Button's tracking token associated with this transaction.
  • pub_ref: The publisher token set when a Button or Link was fetched (only visible to the Publisher).
  • account_id: Account ID.
  • order_id (optional): Order ID reported by the Merchant. (only available for new-user-order and existing-user-order events and Merchants)
  • button_order_id (optional): Order ID generated by Button. (only available for new-user-order and existing-user-order events)
  • order_currency (optional): The currency of the order. (only available for new-user-order and existing-user-order events)
  • order_total (optional): The total attributable order value of the order, in the smallest decimal currency units. (only available for new-user-order and existing-user-order events)
  • order_line_items (optional): An array of the order's line item details. (only available for new-user-order and existing-user-order events).
    • identifier: The unique identifier of your line item.
    • amount: The line item order value, in smallest decimal currency units. Will be an integer >= 0. Example: 1234 for $12.34.
    • quantity (optional): The number of units of this line item. Will be an integer > 1
    • description (optional): Text describing the line item.
    • attributes (optional): A key/value store for strings. Defaults to an empty object.
  • publisher_organization: The organization ID of the application that drove the user to another application.
  • publisher_customer_id (optional): The publisher's internal customer ID (only available for the publisher organization, and only if it is configured)
  • button_id: The ID of the button.
  • commerce_organization: The organization ID of the application that the user was driven into.
  • status: The current state of the transaction.
    • pending: A transaction has been created, but it is subject to change. app-install events skip this state.
    • validated: A transaction is final and will be paid on a later date.
    • declined: A transaction has been canceled (this can be from an order being canceled).
  • advertising_id (optional): The device's advertising ID (IDFA on iOS and Google AID on Android). Only available to Merchants, when this value was reported.

Don't be surprised if Button adds some additional fields to help with any necessary debugging (Ex. 'campaign_id')

An example webhook object:

  "event_type": "tx-validated",
  "data": {
    "amount": 1000,
    "order_currency": "USD",
    "modified_date": "2017-01-01T20:00:00.000Z",
    "created_date": "2017-01-01T20:00:00.000Z",
    "order_line_items": [
            "identifier": "sku-1234",
            "amount": 2000,
            "description": "T-shirts",
            "attributes": {
                "size": "M"
    "button_id": "btn-59722058ab439774",
    "order_id": "order-1",
    "account_id": "acc-67a18cc120e5235d",
    "btn_ref": "srctok-713c963c1c30c55f",
    "currency": "USD",
    "pub_ref": "publisher-token",
    "status": "validated",
    "event_date": "2017-01-01T20:00:00Z",
    "order_total": 6000,
    "advertising_id": "02840796-66B3-4C96-AF72-84393B4925BF",
    "publisher_organization": "org-1418faaf0504783b",
    "commerce_organization": "org-0a94e4380a410aab",
    "button_order_id": "btnorder-4db429a36440c00e",
    "publisher_customer_id": "6815467b-93ca-47ce-97ae-bcf8b4292a87",
    "id": "tx-070dd05f5db6e4ec",
    "category": "new-user-order",
    "validated_date": "2016-06-01T19:02:09Z"
  "id": "hook-7486e4aab57bb23e",
  "request_id": "attempt-5c302d097767a327"

Filter webhook payload for "order" events

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. Depending on how and what you're using Button webhooks for, you'll want to appropriately filter out webhook requests.

For example, if you’re rewarding users on purchases they make, you’ll want to filter out the webhook requests representing app-installs by checking the value of the category field in the request payload. If the value is app-install, ignore the request. If the value is new-user-order or existing-user-order, process the request into user rewards.

Validate payload request data

When validating the data in the webhook request payload, we recommend you verify the availability of specific fields you need instead of validating against a specific JSON structure. This is because the JSON structure may change as we enhance the data reported to us by merchants (e.g. "Jet now reports UPC data with each line-item"). This approach will ensure that your webhooks don’t error when such an update is made.


To acknowledge that you have successfully received a webhook, you should respond with a 2XX status code. Anything else will be considered a failure and the message will be re-sent.

Unsuccessful webhooks will be re-sent for up to 3 days. Retries follow an exponential back-off strategy for the first hour. After that, retries happen once an hour.

To maintain the proper receipt order of messages, new webhooks will only be sent once previous ones are acknowledged. Therefore, if you don't acknowledge a webhook, you won't be bombarded by new webhooks, but you also won't receive recent information.

Error handling

Button’s webhooks enforce a sequential delivery of commissionable events. This means that before we move-on and report the "next" event that occurred, we require confirmation that the "current" webhook was properly accepted.

For this reason, it’s important to respond to a webhook request with a 2XX level HTTP response code, unless a real error occurred requiring subsequent webhooks to be paused until this error is resolved.

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

  • 200 or 204 - Webhook accepted and successfully processed.
  • 202 - Webhook accepted but processing was unsuccessful.
  • 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 happen once an hour up to 3 days.

We recommend logging 202 responses and flagging them up to our Customer Service team for further investigation.


To receive Webhooks from Button, you must expose a public route on the open internet. Because sensitive business-logic often sits behind a webhook handler, it's important to verify that a request received in your webhook endpoint is actually from Button.

To do this, 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.

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 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' }));'/webhook', function(req, res) {
  // Here is the webhook data
  var data =;


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)
require 'sinatra'

require 'openssl'
require 'json'

# You can find your Webhook's Secret in the Dashboard at on the page for a specific Webhook.
WEBHOOK_SECRET = 'YOUR_WEBHOOK_SECRET' # Do not publicly expose this

post '/webhook' do
  request_body =
  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

def signature request_body

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 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(
    sent_signature = request.headers.get('X-Button-Signature').encode('utf8')

    if not hmac.compare_digest(computed_signature, sent_signature):

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

    return 'ok', 200

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

if __name__ == "__main__":

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