Using Webhooks

One of the ways you would integrate your solutions with Subsbase is through Webhooks (also sometimes called Callbacks). Webhooks are triggered based on the configurations made in the Settings > Event Notifications page on your Admin Portal, where you can specify which events trigger a webhook and configure the respective parameters.

Webhook Configuration

Endpoint

  • This is where you would enter the endpoint where you want the webhook to be triggered. Typically, this endpoint is exposed on your backend. Ensure that the endpoint is accessible from the public internet.
  • You can customize the webhook endpoint by utilizing the placeholders designed for each event in Event Notifications.

It might be useful to set up 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 have the flexibility to choose any of the HTTP Methods (Verbs) that support request bodies, such as PUT, POST, and PATCH.

Custom Headers

You have the option to specify headers to be included in the Webhook HTTP Request. These headers can contain details about the information source or authorization, among other possibilities. However, it is important to note that you cannot use the same header key more than once.

Payload

When triggering the webhook, you have the flexibility to choose what to include in the data payload you receive. This allows you to control the payload and prevent unnecessary increase in its packet size.

Webhook HTTP Request

The webhook will be triggered according to the specified configuration, as outlined in the Webhook Configuration section. To ensure the authenticity of the included data, a special header containing a signature is utilized, as explained in the Webhook Signature section. The signature is included as a signature header within 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"
      }
    ]
  }
}

Given the unstable nature of wide-area network connections and the potential disruptions caused by various factors, a webhook request relies on receiving a success response with an HTTP status code within the range of 200-299. If the response does not indicate successful processing on your end, the webhook will be retried up to three times. Each retry is spaced out with a delay of 30 minutes, 60 minutes, and 90 minutes, respectively, following the previous attempt.

Webhook Signature

To guarantee the authenticity of the webhook and prevent any modifications during transmission, 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 handled with the same level of security as a password. It should never be shared or hardcoded in your codebase repositories. Failure to keep the webhook secret confidential could expose your backend services to fraudulent information from attackers or hijackers, potentially impacting the accuracy of subscriber data.

If you suspect that the webhook secret has been compromised during testing or development, it is advisable to regenerate a new secret easily from the Settings > Webhook and API Settings page in your Admin Portal.

To ensure the authenticity of the received payload, you should compute the hash yourself using the provided examples below.

  • Utilize the received payload and your existing webhook secret to calculate the hash.
  • Then, compare the resulting hash with the value in the signature header.
    • If they match, you can confidently consider the payload as authentic and process it as required.
    • However, if they do not match, it is recommended to omit the request.
  • If you suspect that the webhook secret has been compromised, it is advisable to regenerate a new secret.

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. Initialize an HMAC SHA-256 calculator using the Webhook Secret
     5. Hash the raw JSON content using the HMAC calculator
     6. Check 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 validate 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.