Webhooks

Button Webhooks are an easy way to receive Button events in real-time.

Looking for a reference of all API endpoints? Check out the attribution API reference.

APIs require you to poll for events. To make integrations even easier you can configure webhooks. With webhooks, Button will push new events to your system in real-time.

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.

Format

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

The headers will include

Acknowledgment

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.

Event Types

Event Objects

Transaction

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.

An example object

{
    "id": "tx-070dd05f5db6e4ec",
    "amount": 600,
    "currency": "USD",
    "category": "new-user-order",
    "created_date": "2016-06-01T19:02:09Z",
    "modified_date": "2016-06-01T19:02:09Z",
    "validated_date": "2016-06-01T19:02:09Z",
    "event_date": "2016-06-01T19:01:50Z",
    "button_order_id": "btnorder-4db429a36440c00e",
    "order_id": "order-1",
    "account_id": "acc-123",
    "order_currency": "USD",
    "order_total": 6000,
    "order_line_items": [
        {
            "identifier": "sku-1234",
            "amount": 2000,
            "quantity":3,
            "description": "T-shirts",
            "attributes": {
                "size": "M"
            }
        }
    ],
    "btn_ref": "srctok-713c963c1c30c55f",
    "publisher_organization": "org-63cd58a1a2a8b543",
    "publisher_customer_id": "6815467b-93ca-47ce-97ae-bcf8b4292a87",
    "button_id": "btn-59722058ab439774",
    "commerce_organization": "org-44ae5a8149efe050",
    "status": "validated",
    "advertising_id": "02840796-66B3-4C96-AF72-84393B4925BF"
}

Security

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.

Example Implementations

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.

Python

For generic use in any web framework:

import os
import hmac
import hashlib

WEBHOOK_SECRET = os.environ['WEBHOOK_SECRET']

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

For use in a Flask application:

import hmac
from flask import Flask, request, abort

app = Flask(__name__)

@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)

  return 'ok'
node.js

For generic use in any web framework:

var crypto = require('crypto');

var WEBHOOK_SECRET = process.env['WEBHOOK_SECRET'];

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

For use in an express application:

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

var app = express();

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

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

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

You can find your Webhook's Secret in the dashboard on the page for a specific Webhook.

Changelog

2016-12-27

2016-09-16

2016-07-06

2016-06-22

2016-06-01

2016-05-10