How to Use Paystack's Webhooks in Node.js
Published: 2024-01-01 08:17:31

This article assumes you are competent with Node.js, Git, and Github. It also assumes you have and can use an API-testing client like Insomnia or Postman.

## Introduction
This is a guide on integrating payments via Paystack into your backend, using **Node.js**. I set up a bare-bones repo specifically for this tutorial, and you can check it out [here](https://github.com/Chiemezuo/paystack-webhooks-tutorial). I intentionally made it as simple as possible, so you can focus on the main content, without needing to sift through folder structures. Now, let's get to the basics.

When a transaction is made via Paystack, there are two primary ways of knowing its completion status: 
1. Callback URLs.
2. Webhook URLs.

To put it in simple words, a Webhook URL is just a URL or link. To put it in less simple words, a Webhook URL is a link that a service (in this case, Paystack) can send data to. The data it sends is an event that tells you (who owns that link), that the operation has been completed. An analogy of this is sending a letter to someone through the post office (initiating a transaction), and getting a letter from them mailed back to your address (a Webhook URL). With Webhooks, the only thing Paystack requires of you is that you send a `200` response code. 

Callback URLs are a bit more direct but less versatile, and this [article](https://support.paystack.com/hc/en-us/articles/7442196752540-How-to-set-up-and-use-Paystack-Callback-URL#:~:text=transaction%20was%20successful.-,Why,-Webhooks%20are%20preferred) explains why.

## Requirements
For this tutorial, aside from the requirements listed on the repo page, you will need a Paystack [account](https://dashboard.paystack.co/#/signup) set up. You'll also need a live backend service running with any host of your choice. 

> **Note:** You cannot use **localhost** for the Webhook URL.

## Guide
To be on the same page, I recommend forking and cloning the repo [here](https://github.com/Chiemezuo/paystack-webhooks-tutorial).
Steps:
* After cloning the repo into your local machine, run the installation command for npm: `npm install`. This will install all the project dependencies.

* Create a `.env` file. Do this by copying the content of the `.env.example` file and modifying it appropriately. Your `.env` file should end up with a similar-looking format:
```sh
PORT=3000 # port number
PAYSTACK_SECRET_KEY=<paystack_sk_test_key> # paystack secret key
```

> **Note:** Make sure you're using the **test** key for testing purposes, and not the production key.
To get your test key, this [article](https://support.paystack.com/hc/en-us/articles/360009881600-Paystack-Test-Keys-Live-Keys-and-Webhooks#:~:text=test%20payment%20page.-,How,-to%20get%20your) will walk you through the process.

* Now, let's examine the `app.js` file. An **initializationUrl** is defined for working with Paystack's API, and so are some very specific headers, as per Paystack's documentation. 
```js
const initializationUrl = 'https://api.paystack.co/transaction/initialize'
const headers = {
  Authorization: `BEARER ${process.env.PAYSTACK_SECRET_KEY}`,
  'Content-Type': 'application/json',
}
```
There are two very important routes in the `app.js` file. The first one is for initiating a fund request. It takes in a payload consisting of an `email` and `amount` in a subunit of the supported currency. These are the only required parameters, but you can find a list of some additional optional parameters [here](https://paystack.com/docs/api/charge/#create:~:text=to%20application/json-,Body%20Parameters,-email). 
```js
app.post('/fund', async (req, res) => {
  const payload = {
    email: req.body.email,
    amount: req.body.amount
  }
// more code ...
```
After the payload has been created, you send a request to the `initializationUrl`, passing it the payload, and the headers you defined. I recommend you incorporate error handling in case the request fails, to avoid server crashes and to be able to better identify issues. We would have something like this:
```js
// ... payload data
try {
    const response = await axios.post(initializationUrl, payload, { headers })
    return res.status(201).json({ status: true, data: response.data })
} catch(e) {
    return res.status(400).json({ status: false, message: 'Funding failed' })
}
```
> **Note:** I used `axios` instead of `fetch` for simplicity, but the latter would still work just as fine.

We will be left with the second important route: the Webhook. The webhook only requires one thing from that route: a response of `200`. However, accounting for the fact that you might want to do something or call a process when the Webhook is called, I added some boilerplate code. It gets the signature from the header that Paystack will pass along with the request, it compares the hash of the request body sent to you with the signature (using your secret key), and it leaves a little wiggle room for you if you are interested in adding some more logic if the hash matches the signature from the header. 
```js
// the Webhook for receiving Paystack's response
app.post('/paystack-webhook', async (req, res) => {
  const paystackSignature = req.headers['x-paystack-signature']
  const secret = process.env.PAYSTACK_SECRET_KEY

  const hash = crypto.createHmac('sha512', secret).update(JSON.stringify(req.body)).digest('hex')
  if (hash == paystackSignature) {
    
    // Retrieve the request's body
    const event = req.body
    // Do something with the event ...

    // You can log to the console to be sure
    console.log('The webhook was called with event:', event)
  }

  // A status response of 200 must be sent back to the Paystack 
  res.status(200).json({
    status: true,
    message: 'Received Paystack Event'
  });

})
```

* Deploy your backend service to any web host. [Cyclic](https://www.cyclic.sh/) was a really good option for me.

* After deploying, test that it's properly set up. To be sure the service is working properly, visit the base URL. For example; `https://<your-url>/`.
You should see a ` "Welcome to the Paystack tutorial" `.
If you do, the service is working fine.

* With the service up and running properly, copy the URL to the Webhook, and add it to your Paystack account settings, under the 'API Keys and Webhooks' section. 
> **Note**: Put this as the Webhook URL for **test** mode.

* Now, for testing with an API client (Insomnia, in this case), you pass in a JSON payload to the `fund` route. This payload consists of your email and the amount in question.
```js
{
	"email": "<your-mail>@mail.com",
	"amount": 2000
}
```
If successful, you should get a response that looks like the snippets by the right side of [this](https://paystack.com/docs/api/transaction/#:~:text=1-,%7B,%7D,-Verify%20Transaction).

* In the response you've received, there is an `authorization_url`. Open it to complete the transaction. On completing the transaction, you can close the browser page.
> **Note**: Since this is still test mode, trying to simulate a scenario where the transaction fails doesn't do anything.

* Finally, you should go to the logs of your hosted application. Shortly after, you should see a logged statement indicating that the Webhook was called. You can also verify this by going to your Paystack dashboard and switching from live mode to test mode to see your most recent transaction.

That just about settles it, as far as working with Paystack's Webhooks is concerned.
Thank you for reading. 

Cheers.