# Overview

Webhooks enable you to receive notifications upon certain webstore events, for example when a new payment is made. We will send a HTTP request containing a JSON object to any configured endpoints that are subscribing to the respective event. Webhooks are a great way to integrate Tebex with your own website, forum or internal database.

## How To Setup Webhooks Endpoints

1. Go to **Developers** > **Webhooks** > **Endpoints**.
2. Click **Add Endpoint**.
3. Enter the URL of your webhook endpoint.
4. Select the types of webhooks that you'd like to subscribe to.
5. Click **Add**.&#x20;

After adding your endpoint, you will receive a warning to inform you that we haven't been able to validate endpoint. Please see the section below for further information about handling validation webhooks.

## Handling Validation Webhooks

Webhooks will not be sent to an endpoint unless it has been validated first. This is to ensure we are not sending HTTP requests to URLs that are not expecting our webhooks.

The validation webhook that we send will look similar to the example below. To identify a validation webhook you can check the `type` property in the JSON body.

```json
{
  "id": "2c116b11-1110-91e0-b266-b792c8da5f11",
  "type": "validation.webhook",
  "date": "2021-08-24T12:21:47+00:00",
  "subject": {}
}
```

Upon receiving a validation webhook you must respond with a **200 OK** response containing a JSON object that has an `id` property representing the validation webhook's ID, please see the example response below.

```json
{
  "id": "2c116b11-1110-91e0-b266-b792c8da5f11"
}
```

Once your endpoint is setup to successfully handle validation webhooks, please visit **Developers** > **Webhooks** > **Endpoints** and click the **Validate** button next to the endpoint to re-send the validation webhook.

## Verifying Webhook Authenticity

### IP Address

Webhooks from Tebex will only ever be sent from the two IP addresses listed below.

**18.209.80.3**\
**54.87.231.232**

When building your webhook endpoint, we suggest that you check the IP address of the sender and throw a 404 Not Found error if the IP address isn't in the above list.&#x20;

### Signature

In addition to checking the IP address, we strongly advise that you verify the **X-Signature** header that we send with all requests.

The signature is generated by SHA256 hashing the JSON body and then building a SHA256 HMAC with the body hash as the data/content and your webhook secret as the key.&#x20;

Your webhook secret is displayed on the **Developers** > **Webhooks** > **Endpoints** page. Please see our example code snippets below of how to generate the signature.

{% tabs %}
{% tab title="PHP" %}

```php
$json = file_get_contents('php://input');
$secret = "0d45982a10e3a072d0c1261c55dd9918";
$signature = hash_hmac('sha256', hash('sha256', $json), $secret);
```

{% endtab %}

{% tab title="Node.js (Express)" %}

```javascript
var bodyParser = require('body-parser');

var app = express();

app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf
  }
}));

router.post('/webhook', function(req, res, next) {
  const secret = 'xxxxxxxxxxx';

  const bodyHash = crypto
      .createHash('sha256')
      .update(req.rawBody.toString(), 'utf-8')
      .digest('hex');
  const finalHash = crypto.createHmac('sha256', secret)
      .update(bodyHash)
      .digest('hex');
  console.log('finalHash', finalHash)

  // Compare hash...
});
```

{% endtab %}
{% endtabs %}

�If the signature that you generate doesn't match the **X-Signature** header, please disregard the webhook because it is not from Tebex.

{% hint style="info" %}
Some frameworks such as Express (Node.js) automatically parse JSON request bodies which can result in a signature mismatch. Please ensure the signature is calculated using the raw request body and not a parsed version of the body.
{% endhint %}

## Handling Webhooks

All of our webhooks are sent using the standardised format with the following properties:

**id**: This property represents the unique ID of the webhook.\
[**type**](#webhook-types): The type of webhook we are sending, for example *payment.completed*.\
**date**: This is the date that the webhook was generated.\
[**subject**](#webhook-subjects): The data within this property will differ depending on the *type* of webhook. To view the different subject formats please see the subject types below.

When handling a webhook, please ensure you respond with a 2XX status code (anywhere between 200-299). If your endpoint responds with any other status code we will automatically try to resend the webhook later-on.&#x20;

After several attempts of retrying we will mark the webhook as failed and your endpoint may require validating again.

### Webhook Types

* **payment.completed**\
  Sent when a payment is completed.
* **payment.declined**\
  Sent when a payment is declined.
* **payment.refunded**\
  Sent when a payment is refunded.
* **payment.dispute.opened**\
  Sent when a dispute is opened against a payment.
* **payment.dispute.won**\
  Sent when a dispute against a payment is won.
* **payment.dispute.lost**\
  Sent when a dispute against a payment is lost.
* **payment.dispute.closed**\
  Sent when a dispute against a payment is closed.
* **recurring-payment.started**\
  Sent when a recurring payment starts.
* **recurring-payment.renewed**\
  Sent when a recurring payment renews.
* **recurring-payment.ended**\
  Sent when a recurring payment ends and the purchased products should be revoked from the customer.
* **recurring-payment.cancellation.requested**\
  Sent when a customer requests to cancel a recurring payment. The cancellation will take place at the end of the current billing period (i.e. when renewal would typically occur) and a *recurring-payment.ended* webhook will be sent.&#x20;
* **recurring-payment.cancellation.aborted**\
  Sent when a customer aborts the outstanding cancellation request. The recurring payment will renew as normal.&#x20;
* **validation.webhook**\
  Sent when initially adding a new webhook endpoint.

### Webhook Subjects

{% tabs %}
{% tab title="Payment" %}

<pre class="language-json"><code class="lang-json">{
  "transaction_id": "tbx-xxxxxxxx",
  "status": {
    <a data-footnote-ref href="#user-content-fn-1">"id": 2</a>,
    "description": "Refund"
  },
  "payment_sequence": "oneoff",
  "created_at": "2021-08-19T13:03:30.000000Z",
  "price": {
    "amount": 13.2,
    "currency": "GBP"
  },
  "price_paid": {
    "amount": 13.2,
    "currency": "GBP"
  },
  "payment_method": {
    "name": "PayPal",
    "refundable": true
  },
  "fees" : {
    "tax" : {
      "amount" : 0,
      "currency" : "USD"
    },
    "gateway" : {
      "amount" : 0.73,
      "currency" : "USD"
    }
  },
  "customer": {
    "first_name": "Test",
    "last_name": "Test",
    "email": "test@test.com",
    "ip": "1.2.3.4",
    "username": {
      "id": "1234",
      "username": "Test"
    },
    "marketing_consent": true,
    "country": "GB",
    "postal_code": "TE57 1NG"
  },
  "products": [
    {
      "id": 4,
      "name": "Example Package",
      "quantity": 1,
      "base_price": {
        "amount": 11,
        "currency": "GBP"
      },
      "paid_price": {
        "amount": 11,
        "currency": "GBP"
      },
      "variables": [
        {
          "identifier": "server",
          "option": "3"
        }
      ],
      "expires_at": null,
      "custom": null,
      "username": {
        "id": "1234",
        "username": "Test"
      }
    }
  ],
  "coupons": [],
  "gift_cards": [],
  "recurring_payment_reference": null,
  "decline_reason": {
    <a data-footnote-ref href="#user-content-fn-2">"code": "incorrect_card_information"</a>,
    "message": "You entered incorrect card information. Please check your card details are correct and then try again."
  },
  "creator_code": "example",
  "settled_at": "2021-08-21T13:03:30.000000Z"
}
</code></pre>

#### **Useful Status IDs**

<table><thead><tr><th width="118">ID</th><th>Status</th></tr></thead><tbody><tr><td><code>1</code></td><td>Complete</td></tr><tr><td><code>2</code></td><td>Refund</td></tr><tr><td><code>3</code></td><td>Chargeback</td></tr><tr><td><code>18</code></td><td>Declined</td></tr><tr><td><code>19</code></td><td>Pending Checkout</td></tr><tr><td><code>21</code></td><td>Refund Pending</td></tr></tbody></table>

#### **Decline Reasons**

| Code                         | Message                                                                                                                                                                                                                                                              |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rejected`                   | The payment method was declined by the customers payment method provider. We recommend asking the customer to contact their bank/provider to have this issue resolved.                                                                                               |
| `fraud_card_issuer`          | The payment method was declined by the customers card issuer or their bank due to suspected fraud. We recommend asking the customer to contact their bank to have this issue resolved.                                                                               |
| `fraud_tebex`                | Tebex implements artificial intelligence to determine if a transaction is fraudulent. Based on the data we have collected, we have decided this transaction to be too risky to accept. Do not tell the customer that they have been blocked due to fraud.            |
| `incorrect_card_information` | The customer has entered incorrect card details during the checkout process. Please advise the customer to try again with the correct details.                                                                                                                       |
| `network_failure`            | There was a failure in the payment network. Please advise the customer to try again later.                                                                                                                                                                           |
| `insufficient_funds`         | The payment method was declined due to the customer having insufficient funds.                                                                                                                                                                                       |
| `fraud_history`              | Tebex reviews the fraud history of customers as part of our fraud assessment. Based on the data we have collected, the fraud history of this customer makes this transaction too risky to accept. Do not tell the customer that they have been blocked due to fraud. |
| {% endtab %}                 |                                                                                                                                                                                                                                                                      |

{% tab title="Recurring Payment" %}

<pre class="language-json"><code class="lang-json">{
  "reference": "tbx-r-xxxxx",
  "created_at": "2021-08-23T11:33:51.000000Z",
  "next_payment_at": "2021-09-23T11:34:13.000000Z",
  "status": {
    <a data-footnote-ref href="#user-content-fn-3">"id": 5</a>,
    "description": "Cancelled"
  },
  "initial_payment": <a data-footnote-ref href="#user-content-fn-4">&#x3C;Payment Subject></a>,
  "last_payment": <a data-footnote-ref href="#user-content-fn-4">&#x3C;Payment Subject></a>,
  "fail_count": 0,
  "price": {
    "amount": 20,
    "currency": "GBP"
  },
  "cancelled_at": "2021-08-24T14:46:05.000000Z",
  "cancel_reason": "Cancelled by webstore on request"
}
</code></pre>

#### **Useful Status IDs**

<table><thead><tr><th width="118">ID</th><th>Status</th><th width="312">Description</th><th data-type="checkbox">Active</th></tr></thead><tbody><tr><td><code>2</code></td><td>Active</td><td>The recurring payment is active.</td><td>true</td></tr><tr><td><code>3</code></td><td>Overdue</td><td>We couldn't charge the customer for the renewal, but we will automatically retry tomorrow.</td><td>true</td></tr><tr><td><code>4</code></td><td>Expired</td><td>After multiple attempts, we couldn't renew the recurring payment.</td><td>false</td></tr><tr><td><code>5</code></td><td>Cancelled</td><td>The recurring payment has been cancelled as requested by the creator/customer.</td><td>false</td></tr><tr><td><code>7</code></td><td>Pending Downgrade</td><td><p>Only applies to recurring payments for packages in Tiered Categories:</p><p></p><p>The customer has requested to downgrade their recurring payment to a lower priced package.</p></td><td>true</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

## Testing Webhooks

Our webhook testing feature allows you to manually trigger webhooks from your control panel. Please follow the steps below to send a test webhook.

1. Navigate to **Developers** > **Webhooks** in your control panel.
2. Click the **Send Test** button.
3. Choose the type of webhook you'd like to send.
4. Provide a valid transaction ID if you've selected a payment webhook type, or enter a recurring payment reference if you've chosen a recurring payment webhook type.
5. Click the **Send Test** button.

[^1]: [#useful-status-ids](#useful-status-ids "mention")

[^2]: [#decline-reasons](#decline-reasons "mention")

[^3]: [#useful-status-ids-1](#useful-status-ids-1 "mention")

[^4]: [#payment](#payment "mention")
