## Creating a Webhook

- Creating a Button Webhook completed within the [Button Dashboard](https://app.usebutton.com/account/login).
- 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](https://app.usebutton.com/account/login).
- 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](https://app.usebutton.com/webhooks).

## 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](https://developer.usebutton.com/guides/publishers/webhooks-getting-started#transaction-details) containing order details |
| `id` | The unique identifier for the webhook event |
| `event_type` | The "state" the transaction event represents (see more about [Event States](https://developer.usebutton.com/guides/publishers/webhooks-getting-started#webhook-event-states) below) |

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

JSON

```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](https://www.iso.org/iso-8601-date-and-time-format.html)). |
| `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](https://developer.usebutton.com/docs/button-webhooks#transaction-category-properties) 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](https://www.iso.org/iso-8601-date-and-time-format.html)). |
| `currency` | The currency of the commission, as a three-letter ISO currency code ( [ISO 4217](http://www.xe.com/iso4217.php)). |
| `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](https://www.iso.org/iso-8601-date-and-time-format.html)). |
| `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](https://www.iso.org/iso-8601-date-and-time-format.html)). |
| `order_currency` | The currency of the order. (only available for `new-user-order` events). ( [ISO 4217](http://www.xe.com/iso4217.php)).<br>\* **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](https://developer.usebutton.com/docs/button-webhooks#transaction-order-line-items-properties). 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](https://www.iso.org/iso-8601-date-and-time-format.html)). |
| `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](https://developer.usebutton.com/docs/button-webhooks#transaction-status-properties). |
| `validated_date` | When the transaction moved to `validated` status. ( [ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html)). |

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

```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](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.1).

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:

```undefined
$ 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:

- [Node.js](https://github.com/button/button-client-node#iswebhookauthentic)
- [Python](https://github.com/button/button-client-python#is_webhook_authentic)
- [Ruby](https://github.com/button/button-client-ruby#webhook_authentic)

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](https://github.com/button/webhook-test-apps/tree/master/node), [Ruby](https://github.com/button/webhook-test-apps/tree/master/ruby), and [Python](https://github.com/button/webhook-test-apps/tree/master/python), respectively:

JavaScript

```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

```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

```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](https://github.com/button/webhook-test-apps).
