Webhook Semantics

Heres a few tips to ensure you are working with Button Webhooks as efficiently as possible.

Zero Dollar Line Items

In some cases, Brands will report line items with a $0 unit cost to Button, and are specified in the amount of an item in order_line_items. This may represent an item which was provided to the purchaser at no cost (e.g. a sample) or an item which was paid for using rewards. These items will be reported to you as a Publisher in Transaction Webhooks, to ensure that you have a complete view of the purchase made.

Brands report a variety of different identifiers and descriptions for $0 line items. As a Publisher, you should not depend on the structure or content of these fields. It is possible for a line item with a $0 unit cost to have a non-zero commission associated with it. It is possible for an item to transition to $0 unit cost in an update.

This example illustrates an order containing a free sample. Some fields have been omitted for brevity:

{
      "id": "hook-XXX",
      "request_id": "attempt-XXX",
      "event_type": "tx-pending",
      "data": {
        "order_total": 2000,
        "amount": 200,
        "order_currency": "USD",
        "status": "pending",
        "id": "tx-1234",
        "order_line_items": [
          {
            "amount": 2000,
            "identifier": "SKU1234",
            "description": "Shampoo",
            "quantity": 1,
            "publisher_comission": 200,
            "attributes": {
            }
          },
          {
            "amount": 0,
            "identifier": "SKU8052",
            "description": "Soap (Sample)",
            "quantity": 1,
            "publisher_comission": 0,
            "attributes": {
            }
          }
        ]
      }
    }

Flat Rate Discount

In some cases, Brands will offer a flat rate discount on a purchase - for example “$10 off your first purchase”.

This is either represented by reducing the unit price (amount) of each affected item, or more often, by including a negative line item which reduces the total cost of the order by the amount of the discount applied. This negative line item may also have a negative commission associated with it.
Brands report a variety of different identifiers and descriptions for discounts. As a Publisher, you should not depend on the structure or content of these fields.

This example illustrates an order for which a flat rate discount is applied. Some fields have been omitted for clarity:

{
      "id": "hook-XXX",
      "request_id": "attempt-XXX",
      "event_type": "tx-pending",
      "data": {
        "id": "tx-1234",
        "order_total": 1500,
        "amount": 150,
        "order_currency": "USD",
        "status": "pending",
        "order_line_items": [
          {
            "amount": 2000,
            "identifier": "SKU1234",
            "description": "Shampoo",
            "quantity": 1,
            "publisher_comission": 200,
            "attributes": {
            }
          },
          {
            "amount": -500,
            "identifier": "Discount",
            "description": "Discount",
            "quantity": 1,
            "publisher_comission": -50,
            "attributes": {
            }
          }
        ]
      }
    }

Other Discounts

In some cases, Brands will offer a percent based discount on all or part of an order - for example "20% off all household products".

This is either represented by reducing the unit price (amount) of each affected item, or by including a negative line item which reduces the total cost of the order by the amount of the discount applied.

Brands report a variety of different identifiers and descriptions for discounts. As a Publisher, you should not depend on the structure or content of these fields.

Both examples below illustrate the application of a 20% discount to a $20 bottle of shampoo.
In this first example, the discount has been modeled by adjusting the unit price of the item. Some fields have been omitted for clarity:

{
      "id": "hook-XXX",
      "request_id": "attempt-XXX",
      "event_type": "tx-pending",
      "data": {
        "id": "tx-1234",
        "order_total": 1600,
        "amount": 160,
        "order_currency": "USD",
        "status": "pending",
        "order_line_items": [
          {
            "amount": 1600,
            "identifier": "SKU1234",
            "description": "Shampoo",
            "quantity": 1,
            "publisher_comission": 160,
            "attributes": {
            }
          }
        ]
      }
    }

In this second example, the discount has been modeled using a negative line item:

{
      "id": "hook-XXX",
      "request_id": "attempt-XXX",
      "event_type": "tx-pending",
      "data": {
        "id": "tx-1234",
        "order_total": 1600,
        "amount": 160,
        "order_currency": "USD",
        "status": "pending",
        "pub_ref": null,
        "order_line_items": [
          {
            "amount": 2000,
            "identifier": "SKU1234",
            "description": "Shampoo",
            "quantity": 1,
            "publisher_comission": 200,
            "attributes": {
            }
          },
          {
            "amount": -400,
            "identifier": "Discount",
            "description": "Shampoo",
            "quantity": 1,
            "publisher_comission": -40,
            "attributes": {
            }
          }
        ]
      }
    }

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 Brands (e.g. " now reports UPC data with each line-item"). This approach will ensure that your webhooks don’t error when such an update is made.

Notable Distinctions:

  • All amounts come through in cents (no decimals). (e.g. a $10.00 order would be "order_total":"1000")
  • order_total is the total order value, amount is the commission amount (e.g. if the commission is 10%, a $10 order would generate a $1 commission - "order_total":"1000" with "amount":"100")

Acknowledgment

To acknowledge that you have successfully received a webhook, you should respond with a 2XX status code.

Unsuccessful webhooks due to connection issues or non-4XX error 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 receive recent that webhook’s updates.

Error Handling

Button's transaction webhooks enforce ordering on a single transaction, but sends webhooks for independent transactions concurrently to maximize throughput. This means that for a webhooks concerning a specific transaction, before the "next" event that occurred, we require confirmation that the "current" webhook was properly accepted. However, webhooks for other transactions can still send as normal. We limit the total number of transactions webhooks we are trying to send in parallel for to an endpoint at any one time.

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 webhook for that transaction to be paused until this error is resolved.

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

Status CodeDescription
200 or 204Webhook accepted and successfully processed.
202Webhook accepted but processing was unsuccessful.
400Webhook is invalid and cannot be processed. Button will not attempt to re-send this webhook.
401Webhook signature is invalid. Button will attempt to re-send this webhook.

Retry Logic

If Button receives a 400 response for a webhook, we will not attempt to re-send that webhook.

If Button receives a 3XX, 4XX (other than 400), or 5XX response for the webhook, we will retry this webhook with an exponential back-off strategy for the first hour. After that, retries happen once an hour up to 3 days.

Button limits the number of webhooks that it tries to send to an endpoint at any one time. If the number of failing webhooks in the retry state for an endpoint exceeds the number that Button attempts to send, Button will not send any new webhooks to that endpoint until at least one of the webhooks in the retry state succeeds or exceeds its retry limit.

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

Delivery Guarantees

Button offers at-least-once delivery guarantees for webhooks. Button will retry webhook delivery until acknowledgment is received. In rare occasions, we might deliver the same webhook more than once. This can happen when one of the servers becomes unavailable to process a message acknowledgement. If that occurs, the webhook delivery might be retried.

While this is rare, it is important that your application does not reprocess duplicate webhooks. You can use id in the webhook field to dedupe, as we will never send 2 different webhooks with the same id value.

Webhooks 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

Follow best security practices and prevent checking your Button Webhook API secret into version control. Instead, read your Webhook Secret from the server's environment.