Using Webhooks

One of the ways you'd be integrating your solutions with SubsBase is through Webhooks (also sometimes called Callbacks). Webhooks would be triggered based on the configurations done in the Settings > Event Notifications page on your Admin Portal, where you can configure which events trigger a webhook and configure the different parameters.

Webhook Configuration

Endpoint

This is where you'd paste the endpoint on which you want the webhook to be triggered. This endpoint is usually exposed on your backend. Make sure the endpoint is accessible from the public internet.

It might be useful to setup a tunnel to your local development environment by using ngrok or have the webhook pointing to Webhook.site during development/testing to review and confirm payloads

Request Method

The webhook is a normal HTTP request with a JSON payload. Thus, you can choose to have the HTTP requested using any of the HTTP Methods (Verbs) that have request bodies i.e. PUT, POST and PATCH.

Custom Headers

You can specify any headers to be included in the Webhook HTTP Request, these can include information about the source of the info, or authorization for example. You are not allowed to specify a header key more than once.

Payload

You can choose what to include in the data payload you receive when triggering the webhook. This is to avoid an increased packet size of the payload.

Webhook HTTP Request

The webhook will be triggered based on the configuration, as detailed in the Webhook Configuration section. In addition to a special header with a signature that confirms authenticy of the included data as explained in the Webhook Signature section. The signature in included signature header as part of the request headers

The JSON payload in the request will be in the following format

{
  "id": "sb_wh_test-site_1623150633112", // an auto generated id of the specific http request
  "triggerEvent": "testing-webhook", // the event that triggered webhook: new-subscription, cancel-subscription, renew-subscription, pause-subscription, new-customer-portal-user
  "utcTimestamp": "2021-06-08T11:10:33.1126784Z", // UTC timestamp of the request, in ISO-8601 format
  "trial": 0, // trial count of the current request
  "data": {
    "customer": {
      "id": "",
      "emailAddress": "",
      "firstName": "",
      "lastName": "",
      "infoFields": [
        {
          "name": "field",
          "label": "Label",
          "value": "value"
        }
      ],
      "customFields": {
        "key": "value"
      }
    },
    "plan": {
      "planCode": "",
      "name": "",
      "billingCycle": {
        "duration": 1,
        "unit": "Month"
      },
      "billingType": "Recurring",
      "pricingModel": "Fixed",
      "pricing": {},
      "trialPeriod": {
        "duration": 1,
        "unit": "Month",
        "requiresCreditCard": true
      },
      "productName": "",
      "status": "Active"
    },
    "addOns": [
      {
        "code": "",
        "name": "",
        "billingCycle": {
          "duration": 1,
          "unit": "Month"
        },
        "billingType": "Recurring",
        "pricingModel": "PerUnit",
        "pricing": {},
        "status": "Active"
      },
      {
        "code": "",
        "name": "",
        "billingCycle": {
          "duration": 1,
          "unit": "Month"
        },
        "billingType": "Recurring",
        "pricingModel": "PerUnit",
        "pricing": {},
        "status": "Active"
      }
    ]
  }
}

Due to the unstable nature of wide-area network connections and the expected disrupt of connection due to different factors. A webhook request expects a success repsonse by receiving a HTTP status code in the 200-299 range. If the response doesn't indicate successful processing on your side, the webhook will be retried up to 3 times. Each retry is delayed from the previous try by 30mins, 60mins and 90mins respectively.

Webhook Signature

To ensure the webhook is authentic (is originating from our servers) and has not been modified in transit, an HMAC Hash is calculated using the SHA-256 hashing algorithm and included in the http request.

The resulting hash is included in the request headers. A signature header would have the hash value.

The hash is calculated using your Webhook Secret and hashes the entire request body. You can find your webhook secret in the Settings > Webhook and API Settings page on your Admin Portal.

The Webhook Secret should be treated like a password, so it is not to be shared or hardcoded in your codebase repositories. Failure to keep the webhook secret private, could allow attackers or hijackers to push fraudulent information about your subscribers to your backend services. If you feel the webhook secret has been comprised during the testing/development phases, you can easily regenerate a new secret in the Settings > Webhook and API Settings page on your Admin Portal.

You should compute the hash yourself (examples below), using the received payload and your current webhook secret, then compare the result with the value in the signature header. If they match, then the you can consider the payload as authentic and process it as required. If not, then you can omit the request. It is advisable to re-generate the webhook secret if you think it has been compromised.

Psuedo
     1. Get the received HMAC signature from the 'signature' header
     2. Get the raw JSON content from the request body
     3. Retrieve the current Webhook Secret from configuration, environment variables or database
     4. Intialize an HMAC SHA-256 calculator using the Webhook Secret
     5. Hash the raw JSON content using the HMAC calculator
     6. Check that the resulting hash from step 5. matches received HMAC signature
C#
  private string GetReceivedHash() 
  {
    return Request.Headers["signature"]; // This has to be in an MVC Controller since it refers to the inherited 'Request' property
  }

private string SignWebhookPayload(string stringContent, string secret, string receivedHash)
{
using (var hasher = new HMACSHA256(Encoding.ASCII.GetBytes(secret)))
{
var computedHashBytes = hasher.ComputeHash(Encoding.UTF8.GetBytes(payload));
var computedHash = string.Join("", computedHashBytes.Select(b => b.ToString("x2")));

          return computedHash == receivedHash;
      };

} 
PHP
<?php
  $receivedHash = $headers["signature"];
  $webhookSecret= '{your webhook secret}';
  $requestBody = file_get_contents('php://input');
  $computedHash = hash_hmac('sha256', $requestBody, $webhookSecret);
  return hash_equals($signature,$requestSignature) === TRUE;
?>
JS
 function validate(requestBody, webhookSecret, receivedHmac) {
   const calculatedHmac = 
     crypto
       .createHmac("sha256", webhookSecret)
       .update(requestBody)
       .digest("hex")
   return calculatedHmac === receivedHmac
 }
The above code samples are intended to demonstrate how you can validated the request but are not complete implementations of their own. DO NOT USE THEM AS IS
Make sure to test the verification methods thoroughly. Failure to do that, might cause important information to be dismissed