Webhooks

What is a webhook?

A webhook can be considered as an API driven by events rather than requests. Unlike typical APIs, where you would need to poll for data very frequently to get it in real-time, a webhook is a service that allows one program to send automated messages or information to other apps as soon as a particular event takes place. This essentially means that you can get data in real-time without having to poll our events endpoint for it.

Securing your Webhooks

As webhooks deliver data to publicly available URLs in your app, there’s a chance that someone else could find that URL and then provide you with false data. To prevent this from happening, you can employ a number of techniques. The easiest thing to do (and what you should implement before going any further) is to force TLS connections (https). This will ensure that all data sent to your webhook endpoint is encrypted in transit and can’t be intercepted by a third party. You can also verify the webhook’s authenticity by checking the signature header that is sent with each webhook. The signature header is a hash of the payload and a secret key. The secret key is unique to your account and is used to generate the signature. To know more about how to verify the authenticity of a webhook using the signature header, read the signature verification section.

Registering a Webhook

Webhooks are tied to a single application so to register a webhook requires you to have an application on your account. To know more about applications read What is an application?. Once you have an application, you can register a webhook by navigating to the applications tab and clicking on configure button on the application you want to register a webhook for.

Once you have navigated to the application configuration page, you can register a webhook on the webhooks card. The webhook card contains the following fields:

  • URL: This is the URL that will be called when the webhook is triggered. The URL must be a valid URL and must be accessible from the internet.
  • Mode: This is the mode of the webhook. The mode can be either Development or Production.
  • Status: This is the status of the webhook. The status can be either Active or Inactive.

Webhook Events

Webhooks are triggered when a transaction:processed event is fired. The transaction:processed event is fired when a transaction is processed by our system. The event is fired regardless of whether the transaction was successful or not. This event is fired with the following payload:

{
"event_id": "9346978a-40c0-11ed-84d0-dead0b5d6103",
"event_kind": "transaction:processed",
"created_at": "2022-09-30T13:05:36.707853Z",
"data": {
    "ref": "598f7582-ab43-4c90-9575-820806ab9107",
    "kind": "CASHIN",
    "fee": 2.3,
    "merchant": "XXXXX",
    "client": "07XXXXXXXX",
    "amount": 100,
    "provider": "mtn",
    "status": "successful",
    "created_at": "2022-09-30T12:53:50.880947395Z",
    "processed_at": "2022-09-30T13:05:36.706109277Z"
  }
},

The data object contains the details of the transaction that was processed. The data object contains the following fields:

  • ref: This is the reference of the transaction.
  • kind: This is the kind of the transaction. The kind can be either CASHIN or CASHOUT.
  • fee: This is the fee that was charged for the transaction.
  • merchant: This is the merchant that the transaction was made for.
  • client: This is the client that the transaction was made by.
  • amount: This is the amount that was transacted.
  • provider: This is the provider that the transaction was made with.
  • status: This is the status of the transaction. The status can be either successful or failed.
  • created_at: This is the time that the transaction was created.
  • processed_at: This is the time that the transaction was processed.
Behind the scene before we send the webhook payload to your webhook URL, we first ping your webhook URL with HEAD to make sure that it is accessible from the internet. If the ping fails, we will not send the webhook payload to your webhook URL, this is to prevent you from losing our webhook payload. Once we have successfully pinged your webhook URL, we will send the webhook payload to your webhook URL.

Webhook Status

A webhook can be in one of the following statuses:

  • Active: This is the status of a webhook that is currently active.
  • Inactive: This is the status of a webhook that is currently inactive.

The status of a webhook can be changed by navigating to the Webhooks tab on your dashboard and clicking on the Edit button on the webhook that you want to edit. You can then toggle the switch under the Active column to change the status of the webhook.

Deleting a Webhook

You can delete a webhook by navigating to the Webhooks tab on your dashboard and clicking on the Delete button on the webhook that you want to delete.

Testing Webhooks

You can test your webhooks by navigating to the Webhook Tester and copying the URL that is generated. To test your webhooks, you can create a transaction using the Create a Transaction guide. Once you have created a transaction, you can navigate to the Webhook Tester and view the data that was sent to your webhook.

Make sure that you include a X-Webhook-Mode header in your request. The value of the header should be either development or production. This will ensure that the webhook is triggered.

Testing on localhost

If you are testing your webhooks on localhost, you can use localhost.run to expose your localhost to the internet. To use localhost.run run the following command:

ssh -R 80:localhost:8080 nokey@localhost.run
Make sure that you replace `8080` with the port that your application is running on.

This will generate a URL that you can use to test your webhooks. You can then use the URL to test your webhooks. localhost.run is not a service that we recommend for production use. It is only meant for testing purposes.

If you wish to use another service to expose your localhost to the internet, you can read more about it here.

Signature Verification

Webhooks sent by Paypack are verified by calculating a digital signature of the request body using the webhook secret key. Each webhook request includes a x-paypack-signature header, generated using the webhook's secret along with the data sent in the request. To verify the request, you need compute the HMAC digest of the request body using the webhook secret key and compare it with the value of the x-paypack-signature header.

The x-paypack-signature header is a SHA-256 HMAC digest of the request body calculated using the webhook secret key as the key and the request body as the value. The HMAC digest is then converted to a base64 string.

The webhook secret key can be found on the webhook configuration page on your dashboard. Click the copy webhook secret link under the webhook URL field to copy the webhook secret key.

Examples

JavaScript
app.use(
  express.json({
    // Store the rawBody buffer on the request
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);

app.post('/webhook', async (req, res) => {
  //Extract X-Paypack-Signature headers from the request
  const requestHash = req.get('X-Paypack-Signature');

  //secret which you can find on your registered webhook
  const secret = os.GetEnv("PAYPACK_WEBHOOK_SIGN_KEY");

  //Create a hash based on the parsed body
  const hash = crypto.createHmac('sha256', secret).update(req.rawBody).digest('base64');

  // Compare the created hash with the value of the X-Paypack-Signature headers
  if (hash === requestHash || req.Method != "HEAD") {
    //Do your work here!
    console.log('Webhook is originating from Paypack');
  } else {
    console.log('Signature is invalid, rejected');
  }
});
PHP
<?php
$data = json_decode($_POST['json']);
$secret = getenv('PAYPACK_WEBHOOK_SIGN_KEY') ? getenv('PAYPACK_WEBHOOK_SIGN_KEY') : '';

// this checks the X-Paypack-Signature header before processing the body of the POST
$hash = base64_encode(hash_hmac('sha256', $data, $secret));
$requestHash = $_SERVER['X-Paypack-Signature'];
 
if ($hash != $hmacHeader) {
     echo 'Invalid signature';
}else{
      echo 'Valid signature';
}
Go
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  signature := r.Header.Get("X-Paypack-Signature")
  secret := os.GetEnv("PAYPACK_WEBHOOK_SIGN_KEY")

  if signature == "" {
    Respond(w, []byte("missing signature header"), http.StatusBadRequest)
    return
  }

  hasher := hmac.New(crypto.SHA256.New, []byte(secret))

  payload, err := io.ReadAll(r.Body)

  if err != nil {
    Respond(w, err, http.StatusBadRequest)
    return
  }

  r.Body.Close()

  _, err = hasher.Write(payload)

  if err != nil {
    Respond(w, err, http.StatusBadRequest)
    return
  }

  if base64.StdEncoding.EncodeToString(hasher.Sum(nil)) != signature {
            Respond(w, []byte("Signature is invalid, rejected"), http.StatusBadRequest)
    return
  }

  r.Body = ioutil.NopCloser(bytes.NewBuffer(payload))

  next.ServeHTTP(w, r.WithContext(r.Context()))
})
Python
import hmac
import hashlib
import base64

def verify_webhook(data, signature, secret):
    hash = hmac.new(
        secret.encode('utf-8'),
        data.encode('utf-8'),
        hashlib.sha256
    ).digest()
    return hmac.compare_digest(
        signature.encode('utf-8'),
        base64.b64encode(hash).decode()
    )

Troubleshooting

Webhooks not being triggered

If you are not receiving any webhooks, the first thing to check is the URL that you are using. Ensure that the URL is accessible from the internet and that it is a valid URL. If the URL is valid and accessible from the internet, the next thing to check is the application that the webhook is tied to. Ensure that the webhook status is active. If the webhook status is active, the next thing to check is the mode of the webhook. Ensure that the mode used while making requests to our API is set to the same mode on your dashboard.

Our SDKs come pre-configured with the correct mode. If you are using our SDKs on a server the mode will be automatically set to Production while if you are on a localhost the mode will be automatically set to Development.

If you want to override the mode, you can refer to the SDK documentation for the language that you are using.