Webhooks

Webhook notifications

Payabbhi can send a webhook notification to your configured endpoint URL when a specific event occurs within Payabbhi related to your account. This allows your application to respond programmatically to an event within Payabbhi e.g. when the event Order.paid occurs, your application endpoint receives a webhook event and may respond programmatically with an appropriate action like updating your application database.


Events

Payabbhi creates an Event object whenever a business event occurs within Payabbhi e.g. when a successful Payment is made against an Invoice, Payabbhi creates an Event object of type payment.captured.

This Event object describes the event in terms of the event type and the data associated with the event. The data typically contains the state of the entity at the point when the event occurs e.g. payment.captured event contains a payment object and order.paid event contains an order object.


You may access the list of Events at Portal > Developers > Events. Click on any event for the details including the Event object in JSON format.


Usage scenarios

The primary usage scenario of webhooks is when you want your application to receive notification for asynchronous changes. Most changes or events within Payabbhi would happen synchronously e.g. an API invocation would trigger an event with a synchronous response. However there are cases when changes take place asynchronously e.g. when a customer makes a successful payment against an Invoice. In these cases, Webhook events can notifiy your application.


Webhook configuration

You may configure webhook endpoints from Portal > Developers > Webhooks.

  • Add endpoint
    • Provide endpoint URL
    • Choose the specific event/s for receiving webhook notification
  • List configured Webhook endpoints
  • Update or deactivate a Webhook endpoint

Events & webhook configurations are separate for live and test modes. In other words, you need to configure two separate Webhook endpoints even if you want to receive webhook notifications for the same event in both live and test modes.

Payabbhi can be configured to send the same webhook event to multiple endpoints.


Processing a Webhook

When an event occurs, Payabbhi will send the event payload as part of a HTTP POST request to the endpoint/s as per your account’s Webhook configuration. The event payload is sent as JSON in the POST request body. Your application can parse the JSON into an Event object for further processing.


Your application is expected to return 2XX HTTP Status code to indicate successful receipt of the Webhook to Payabbhi within a time-window of 10 seconds. Any other information returned via request headers or in the request body is not considered for determining webhook delivery status.

In case there is a possibility of your application logic resulting in time-out, it is better to return HTTP Status code in the range of 2XX before the processing begins to ensure successful delivery status.


PHP

<?php
// This example uses slim framework (https://www.slimframework.com) to receive and process webhooks

require 'vendor/autoload.php';
use \Slim\App;

$app = new App();

$app->post('/my/webhook/url/', function ($request, $response, $args) {
  // Retrieve the signature from request header
  $signature = $request->getHeaderLine('Payabbhi-Signature');

  $body = $request->getBody();

  // Make sure to set the ACCESS_ID, SECRET_KEY and WEBHOOK_ENDPOINT_SECRET environment variables
  $accessId  = getenv('ACCESS_ID');
  $secretKey = getenv('SECRET_KEY');
  $webhookSecret = getenv('WEBHOOK_ENDPOINT_SECRET');

  try {
    $client = new \Payabbhi\Client($accessId, $secretKey);
    $result = $client->utility->verifyWebhookSignature($body, $signature, $webhookSecret);

    // Now proceed with processing the event
    // ...

  } catch (\Payabbhi\Error\SignatureVerification $e) {
    $er = $response->withStatus(400);
    return $er->write("FAILED");
  }

  // Send a 200 response
  return $response->write("OK");
});

$app->run();

Python

# This example uses Flask framework (http://flask.pocoo.org/) to receive and process webhooks
from flask import Flask
from flask import request
import os
import payabbhi


app = Flask(__name__)

@app.route('/my/webhook/url', methods=['POST'])
def process_webhook():

  # Make sure to set the ACCESS_ID, SECRET_KEY and WEBHOOK_ENDPOINT_SECRET environment variables
  client = payabbhi.Client(access_id=os.environ['ACCESS_ID'], secret_key=os.environ['SECRET_KEY'])

  # Retrieve the signature from request header
  signature =  request.headers.get('Payabbhi-Signature')
  jsonEvent =  request.get_data()
  secret = os.environ['WEBHOOK_ENDPOINT_SECRET']

  try:
    result = client.utility.verify_webhook_signature(jsonEvent,signature,secret)

    # Now proceed with processing the event
    # ...

    except payabbhi.error.SignatureVerificationError as e:
      abort(400)

    # Send a 200 response back
    return 'OK'



if __name__ == '__main__':
	app.run(host='0.0.0.0', port=3000, debug=False)

Java

// This example uses spark framework (http://sparkjava.com/) to receive and process webhooks
import static spark.Spark.*;
import spark.Request;
import com.payabbhi.Payabbhi;
import com.payabbhi.exception.PayabbhiException;


public class Example {
    public static void main(String[] args) {

      // Make sure to set the ACCESS_ID, SECRET_KEY and WEBHOOK_ENDPOINT_SECRET environment variables
      Payabbhi.accessId  = System.getenv("ACCESS_ID");
      Payabbhi.secretKey = System.getenv("ASECRET_KEY");

      port(3000);

      post("/my/webhook/url", (request, response) -> {

        // Retrieve the signature from request header
        String signature = request.headers("Payabbhi-Signature");
        String jsonEvent = request.body();
        String secret = System.getenv("WEBHOOK_ENDPOINT_SECRET");

        try {
      	   boolean result = Payabbhi.verifyWebhookSignature(jsonEvent,signature,secret);

      	    // Now proceed with processing the event
      	   // ...

        } catch (PayabbhiException e) {
            response.status(400);
      	    return "";
        }

        // Send a 200 response
        response.status(200);
        return "OK";
      });
    }
}

.Net

// This example uses .NET Core (https://dotnet.microsoft.com/download) to receive and process webhooks
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Payabbhi;

namespace Example
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Run(async (context) =>
            {
                if (context.Request.Method == "POST")
                {
                    // Retrieve the signature from request header
                    var signature = context.Request.Headers["Payabbhi-Signature"];
                    var jsonEvent = new StreamReader(context.Request.Body).ReadToEnd();

                    // Make sure to set the ACCESS_ID, SECRET_KEY and WEBHOOK_ENDPOINT_SECRET environment variables
                    var accessID      = Environment.GetEnvironmentVariable("ACCESS_ID");
                    var secretKey     = Environment.GetEnvironmentVariable("SECRET_KEY");
                    var webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_ENDPOINT_SECRET");


                    try
                    {
                      Client client = new Client(accessID, secretKey);
                      Boolean result = client.Utility.VerifyWebhookSignature(jsonEvent,
                                                                            signature, webhookSecret);

                      // Now proceed with processing the event
                      // ...

                    }
                    catch (Payabbhi.Error.SignatureVerificationError e)
                    {
                      context.Response.StatusCode = 400;
                      await context.Response.WriteAsync("Failed");
                    }

                    // Send a 200 response back
                    await context.Response.WriteAsync("OK");
                }
                else
                {
                    context.Response.StatusCode = 404;
                    await context.Response.WriteAsync("Not Supported");
                }
            });
        }

    }
}

Node.js

// This example uses express framework (https://expressjs.com/) to receive and process webhooks
const express = require('express')
const app = express()
const bodyParser = require('body-parser');

// Make sure to set the ACCESS_ID, SECRET_KEY and WEBHOOK_ENDPOINT_SECRET environment variables
const payabbhi = require('payabbhi')(process.env.ACCESS_ID, process.env.SECRET_KEY);

app.use(bodyParser.raw({type: 'application/json'}));

app.post('/my/webhook/url', (req, res) => {

    // Retrieve the signature from request header
    var signature = req.get('Payabbhi-Signature');

    try {
        var result = payabbhi.verifyWebhookSignature(
            req.body,  // Contains the event JSON
            signature,
            process.env.WEBHOOK_ENDPOINT_SECRET);

        // Now proceed with processing the event
        // ...

    }catch(e) {
      res.status(400).end()
    }

    // Send a 200 response
    res.json({received: true});
})

app.listen(3000, () => console.log('Running on port 3000'));

Idempotent Processing

It is recommended that your Webhook event processing is idempotent. In other words, your processing logic should consider the possibility of receiving the same event more than once and be capable of ignoring events that have already been processed once.

Webhook Delivery status

Payabbhi attempts to deliver webhooks for up to 24 hours with exponential backoff. Failure to deliver a webhook results in an email notification to the Profile > Contact Details > Email ID.

You may check for Webhook delivery status via Portal > Developers > Events. Click on any event for a list of all attempted Webhook/s and the corresponding status based on the HTTP Status codes received.


Webhook signature verification

In order to help you validate that a webhook event is indeed sent from Payabbhi, we provide a signature in the Payabbhi-Signature header of the HTTP post request. The Webhook signature needs to be verified at the Merchant’s end using server-side code. This verification is typically done using a utility method provided in any one of our Client Libraries.

Webhook Endpoint secret

Each Webhook endpoint has a unique secret. This secret is needed for signature verification. Navigate to Portal > Developers > Webhooks. For any given webhook endpoint, click on the icon in the Secret field to reveal the value.

In case of multiple endpoints, each endpoint will have its own secret and therefore these steps need to be repeated for each endpoint.


Verification using a Client Library

It is recommended to use the utility method provided in any one of our Client Libraries for Webhook signature verification. The utility method needs to be provided with the event payload, the Payabbhi-Signature header, and the endpoint’s secret. If verification fails, an error is returned. You may examine the exception/error details for debugging purposes, typically during development.


Refer to Webhook event processing for full working examples including webhook signature verification.


PHP

$signature = $request->getHeaderLine('Payabbhi-Signature');

$body = $request->getBody();

try {
  $client = new \Payabbhi\Client(<accessId>, <secretKey>);
  $result = $client->utility->verifyWebhookSignature($body, $signature, <WEBHOOK_ENDPOINT_SECRET>);
  // verification passed

} catch (\Payabbhi\Error\SignatureVerification $e) {
  // verification failed
}


Python

  # Retrieve the signature from request header
  signature =  request.headers.get('Payabbhi-Signature')
  jsonEvent =  request.get_data()

  try:
    result = client.utility.verify_webhook_signature(jsonEvent,signature,<WEBHOOK_ENDPOINT_SECRET>)
    # verification passed

  except payabbhi.error.SignatureVerificationError as e:
    # verification failed


Java

      // Retrieve the signature from request header
      String signature = request.headers("Payabbhi-Signature");
      String jsonEvent = request.body();

      try {
    	   boolean result = Payabbhi.verifyWebhookSignature(jsonEvent,signature,<WEBHOOK_ENDPOINT_SECRET>);
    	   // verification passed


      } catch (PayabbhiException e) {
         // verification failed
      }

.Net

      // Retrieve the signature from request header
      var signature = context.Request.Headers["Payabbhi-Signature"];
      var jsonEvent = new StreamReader(context.Request.Body).ReadToEnd();

      try
      {
        Client client = new Client(<accessID>, <secretKey>);
        Boolean result = client.Utility.VerifyWebhookSignature(jsonEvent,
                                                              signature,
                                                              WEBHOOK_ENDPOINT_SECRET);
        // Verification passed

      }
      catch (Payabbhi.Error.SignatureVerificationError e)
      {
        // Verification failed
      }


Node.js

    // Retrieve the signature from request header
    var signature = req.get('Payabbhi-Signature');

    try {
        var result = payabbhi.verifyWebhookSignature(
            req.body,  // Contains the event JSON
            signature,
            <WEBHOOK_ENDPOINT_SECRET>);

        // Verification passed

    }catch(e) {
      // Verification failed
    }

Preventing replay attacks

A replay attack is when a valid payload is intercepted and then re-transmitted with a malicious intent. As a counter-measure against replay attacks, Payabbhi includes a timestamp in the Payabbhi-Signature header. Any change in the timestamp therefore invalidates the signature since the timestamp is part of the signed payload. The timestamp also allows you to optionally add logic to reject a Payabbhi webhook payload even when the signature is valid.

The default value used by our Client libraries for acceptable time interval between the timestamp and the current time is 5 minutes. if you wish to override the default value with your own interval, the utility method for Webhook signature verification accepts an additional parameter replay_interval.

Payabbhi generates the timestamp and signature each time we send an event to your endpoint. This means that every retry attempt for a Webhook event would have a new signature and timestamp.


Manual verification of Webhook signature

The verification is typically done using a utility method provided in any one of our Client Libraries. However, if you wish to create your own implementation, the following section provides the necessary information.

Step 1: Extract timestamp and signature from the header.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature.

 

Step 2: Concatenate the values to get the event payload.

String event_payload =  CONCATENATE(json_payload, "&" , timestamp)

Step 3: Compute the HMAC hex digest using SHA256 algorithm with the concatenated string as message and endpoint secret as key.

generated_signature_at_merchant_end =  hmac_sha256(event_payload, endpoint_secret)

Step 4: Compare signatures & determine if time difference is within permissible limits

If the generated signature at the Merchant’s end matches the signature in the payload, the verification check is a pass. You can now compute the difference between the current timestamp and the payload timestamp and use your own logic to determine if the difference is within permissible time limits.

Boolean matches = (generated_signature_at_merchant_end == payabbhi_signature)
Boolean time_difference_ok =  (current_timestamp - timestamp) > your_replay_interval