Juspay Developer Guide

Welcome to the Juspay Developer Guide. You'll find comprehensive guides and documentation to help you start working with Juspay Docs as quickly as possible, as well as support if you get stuck. Let's jump right in!

Get Started


Payment Flow

The infographic shows the general flow of a system using Express Checkout.

Order Creation

Creating your first Order

Order is the cornerstone for conducting payments with Juspay. Order to Juspay is akin to what a shopping cart to a merchant is. Order encapsulates all the information that is required for payment. All the payments, refunds, etc. are associated with an Order and it becomes the root reference.

Keeping in mind the fundamentals of distributed systems, we have let you choose your order_id when you create the order. This way, you can check the status of your order through our API whenever you want.

To know how to create the order, please refer to our API documentation

Nonetheless, please find the below snippet to quickly create an order. Note that you have to substitute the correct API Key.

curl -k https://api.juspay.in/orders \
    -u your_api_key: \
    -d "amount=10.00" \
    -d "order_id=ord_007" \
    -d "customer_id=guest_user_101" \
    -d "[email protected]" \
    -d "customer_phone=919988665522" \
    -d "product_id=:pid" \
    -d "description=Order Info"

Mandatory Parameters

Different payment gateways & aggregators have a varying set of parameters that are mandatory. Depending on your backend gateway please ensure that the mandatory parameters are sent to avoid any failures.


To understand what each of these parameters is, please refer to our API documentation

Payment redirection

Redirection to Payment Gateway

Once the order has been created, the response will contain the payment link to which you have to redirect the customer for entering payment information.

Example Response

  "order_id": "1478851764",
  "id": "ord_e294a26e66ad4336a992ceab81ad704c",
  "status": "CREATED",
  "status_id": 1,
  "payment_links": {
    "web": "https://api.juspay.in/merchant/pay/ord_e294a26e66ad4336a992ceab81ad704c",
    "mobile": "https://api.juspay.in/merchant/pay/ord_e294a26e66ad4336a992ceab81ad704c?mobile=true",
    "iframe": "https://api.juspay.in/merchant/ipay/ord_e294a26e66ad4336a992ceab81ad704c"

payment_links attribute will be present in both /order/create API and /order/status API. We have provided three variants to make it easy for your customers. As the name implies, these are best suited for the respective channels.

webRenders a desktop-optimized version of the checkout page
mobileRenders a mobile-optimized version of the checkout page
iframeProvides an iFrame that you can embed as part of your checkout page

Payment methods

All the payment methods enabled in your account will be displayed to the user. This is to ensure maximum acceptance which improves your success rate significantly. To enable only required payment methods pass an extra parameter to the payment URL

To enable only Netbanking, pass the following parameter: payment_options=nb. To enable only Wallets, pass the following parameter: payment_options=wallet. To enable Netbanking and Wallets together, pass the following parameter: payment_options=nb|wallet.

Sample code for creating an iFrame with EMI support:

<iframe src="https://api.juspay.in/merchant/ipay/ord_e294a26e66ad4336a992ceab81ad704c?payment_options=nb"
width="630" height="400"
style="border: 1px solid #CCC;padding: 20px;height: auto;min-height: 300px;">


Links become invalid as soon as the order expires. The default expiry is 15 minutes from the time of creation. This value is customizable via our dashboard. Please follow this link to customize it to your need.

The maximum expiry time is 24 hours. This limit is set due to security restrictions. If you wish to extend the expiry period, then please reach out to us. Your chances of convincing us will improve significantly if you use a good random generator for Order ID attribute.

Payment Response

Payment Response
Once the payment is complete the user is redirected to the return_url configured by you. Following is the typical destination where the user is taken to:

HTTP GET https://merchant.shop.com/paymentresponse/handler?order_id=order_id_007&status=CHARGED&status_id=21&signature=euKzwwiUztPPg3MCEYpgKZfcyTr1uQq1hzKkhP8G1vQ%253D&signature_algorithm=HMAC-SHA256

Please note that the parameters are sent using HTTP GET by default. To enable HTTP POST for your MID, please drop an email to [email protected]

Transaction Status codes and their meaning

NEW10Newly created order
PENDING_VBV23Authentication is in progress
CHARGED21Successful transaction
AUTHENTICATION_FAILED26User did not complete authentication
AUTHORIZATION_FAILED27User completed authentication, but the bank refused the transaction
JUSPAY_DECLINED22User input is not accepted by the underlying PG
AUTHORIZING28Transaction status is pending from bank
STARTED20Transaction is pending. Juspay system isn't able to find a gateway to process a transaction
AUTO_REFUNDED36Transaction is automatically refunded
CAPTURE_INITIATED33Capture pending for the pre-authorized transaction
CAPTURE_FAILED34Capture failed for the pre-authorized transaction
VOID_INITIATED32Void pending for the pre-authorized transaction
VOIDED31Void is successful for the pre-authorized transaction
VOID_FAILED35Void failed for the pre-authorized transaction

Transaction is successful only if you receive CHARGED as the value in status. For all other cases, you must assume that the payment has failed or the finite status is not known from the upstream gateway at the moment.

Status Verification

After the redirect, the authenticity should be verified using the signature in the response. The signature parameter in the return_url gives the HMAC signature computed using the algorithm specified by the signature_algorithm parameter. The HMAC is calculated using the following algorithm:

  • Get all the parameters (key=value pairs) from the return_url.
  • It is assumed that the parameters in the return_url are converted into key/value pairs.
  • All parameters except signature and signature_algorithm are used in the following steps.
  • Percentage encode each key and value pairs.
  • Sort the list of parameters alphabetically (ASCII based sort) by encoded key.
  • For each key/value pair:
    • Append the encoded key to the output string.
    • Append the '=' character to the output string.
    • Append the encoded value to the output string.
    • If there are more than one key/value pairs, append a '&' character to the output string.
  • Percentage encode the generated string.
  • The HMAC of the string can be calculated using the Response Key configured in merchant settings.
  • Percentage encode the generated hash, validate against the signature in response (the signature should percentage decoded once before comparing with the generated hash).

To enable the signature generation at JusPay end for the payment response, you must first create a response key under Settings of Juspay dashboard [https://dashboard.expresscheckout.juspay.in/]. Once you have created a key successfully, navigate to Settings-->General (https://dashboard.expresscheckout.juspay.in/) section and select "Yes" for the option "Use signed response".

Once you have completed the above two steps, all the redirection to your website from JusPay will have the signature and the algorithm.

The signature algorithm used by JusPay is HMAC-SHA256. The algorithm is explicitly passed as an argument so that verification is accurate. Newer or more secure algorithms might be introduced in the future.

It is also possible to check the status using the /order/status API. Based on the response object, a success confirmation page or failure message can be shown to the customer. Since this is an authenticated call, done from the server side, signature verification is not required.

#Python example for HMAC signature verification

import urllib
import hmac
import hashlib
import base64

key = 'your_secret_key'
# params := key/value dictionary except `signature`
#           and `signature_algorithm`
# signature := "5ctBJ0vURSTS9awUhbTBXCpUeDEJG8X%252B6c%253D"
# signature_algorithm := "HMAC-SHA256"

encoded_sorted = []
for i in sorted(params.keys()):
    encoded_sorted.append(urllib.quote_plus(i) + '=' + \

encoded_string = urllib.quote_plus('&'.join(encoded_sorted))
dig = hmac.new(key, \
              msg=encoded_string, \

assert urllib.quote_plus(base64.b64encode(dig).decode()) == \
#Ruby example for HMAC signature verification:**
require 'uri'
require 'cgi'
require 'openssl'
require 'Base64'

key = "your_secret_key"
# params := key/value hash except `signature`
#           and `signature_algorithm`
# signature := "5ctBJ0vURSTS9awUhbTBXCpUeDEJG8X%252B6c%253D"
# signature_algorithm := "HMAC-SHA256"

encoded_sorted = []
params.keys.sort.each { |k| encoded_list << URI.encode(k) + \
                        "=" + URI.encode(params[k]) }

encoded_string = CGI.escape(encoded_sorted.join("&"))

hash_string = CGI.escape(Base64.encode64(OpenSSL::HMAC. \
                        digest(OpenSSL::Digest.new('sha256'), \
                        key, data)).strip())
hash_string == URI.decode(return_url). \
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class Util {

     * @param args
    public static void main(String[] args) {
        LinkedHashMap<String, String> param = new LinkedHashMap<String, String>();
        param.put("order_id", ":order_id");
        param.put("status", ":status");
        param.put("status_id", ":status_id");
        System.out.println("param :: "+param);
        String expectedHashparam="OHEZ3sYJa%2F9ZyNZ79u3r4p4F2p9O8%2FjSCcw8ZE5fkr0%3D";
         String scretkey=":Response key";
        try {
            System.out.println(" return value :: "+validateHMAC_SHA256(param,expectedHashparam,scretkey));
        } catch (InvalidKeyException e) {
            // TODO Auto-generated catch block
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
     public static boolean validateHMAC_SHA256(Map<String, String> params, String expectedHash, String key)
                throws java.io.UnsupportedEncodingException, java.security.NoSuchAlgorithmException,
                java.security.InvalidKeyException, java.io.UnsupportedEncodingException {
            if (key == null) return false;

            Map<String, String> sortedParams = new TreeMap<String, String>(params);

            StringBuilder queryStr = new StringBuilder("");
            for (String curkey : sortedParams.keySet())
                queryStr.append(curkey + "=" + sortedParams.get(curkey) + "&");
            queryStr.deleteCharAt(queryStr.length() - 1);
            System.out.println("queryStr ::"+queryStr);
            String message = URLEncoder.encode(queryStr.toString(), "UTF-8");
            Mac hasher = Mac.getInstance("HmacSHA256");
            hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
            byte[] hash = hasher.doFinal(message.getBytes());
            String generatedSign = (URLEncoder.encode(DatatypeConverter.printBase64Binary(hash), "UTF-8"));
            System.out.println("generatedSign ::"+generatedSign);
            return (generatedSign.equals(expectedHash));

function verify_hmac($params, $secret) {
           $receivedHmac = $params['signature'];
           // UrlEncode key/value pairs
           foreach ($params as $key => $value) {
               if($key!='signature' && $key != 'signature_algorithm') {
                   $encoded_params[urlencode($key)] = urlencode($value);
           $serialized_params = "";
           foreach ($encoded_params as $key => $value) {
               $serialized_params = $serialized_params . $key . "=" . $value . "&";
           $serialized_params = urlencode(substr($serialized_params, 0, -1));
           $computedHmac = base64_encode(hash_hmac('sha256', $serialized_params, $secret, true));
           $receivedHmac = urldecode($receivedHmac);
           return urldecode($computedHmac) == $receivedHmac;

You can also read the status from the server using /order/status API explained here. Status along with many other data will be returned as part of the /order/status API. This is an alternative to the GET params in return_url.

Failing to do status verification will result in hackers gaming your system. So, please ensure that status verification is in place before you go LIVE with us.

Example request

curl -X GET \
  https://api.juspay.in/orders/wv_test_ord_110011 \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'version: 2018-10-25' \
  "merchant_id": "wv_test",
  "order_id": "wv_test_ord_110011",
  "customer_id": "[email protected]",
  "product_id": "",
  "status": "CHARGED",
  "status_id": 21,
  "amount": 400,
  "currency": "INR",
  "refunded": false,
  "amount_refunded": 0,
  "return_url": "http://skyview./order/cc-confirm",

The above response is truncated for brevity. However, as you can see above, the status was received as CHARGED. This indicates that the payment was successful and so, you can proceed with the fulfillment of the order.

Status Conflicts

In a typical payment using ExpressCheckout, the control goes from your Checkout Page to Juspay's servers, then to Payment Gateway(PG) or Aggregator, and then to the Bank pages for the second-factor authentication. You will then be redirected to the PG page after the payment is authenticated and authorized, and then back to your inventory confirmation page. In all the above cases there can be timeouts due to bad network or bank/PG downtime. This leads to temporary status conflict between Juspay and/or the PG/bank, as we missed the updated response after the payment.

These conflicts are reconciled periodically when we get the updated status from the PG. The updated status for an order will be notified to the merchant via emails or webhooks. You can also poll the current status of the order using /order/status API to check for an update. This can also be triggered individually for each order, from the order details page in the merchant dashboard.

To enable email alerts, please contact [email protected].

The notification will be sent when the status changes from PENDING_VBV/AUTHORIZATION_FAILED to CHARGED.


After the completion of every payment/refund call, Juspay will provide direct notification to your server regarding the event. These are called Webhooks. You must configure a valid HTTP endpoint that is reachable from our servers to consume these notifications. Our servers will push data using HTTP POST call to your endpoint.

Based on the API version configured in the dashboard, webhooks will be triggered for the events below.

  • Payment Success
  • Payment Failure
  • Refund Success
  • Refund failure
  • Order Creation
  • Transaction Creation
  • Refund Moved to Manual Review.

Why webhooks?

Payment redirection using customers' browsers are not reliable all the time. There would be instances where customers' devices would be on low-quality connections and thereby the final redirection might not succeed. In such cases, a webhook call can help you complete the order for the customer.

But care must be taken while consuming the webhook data. Since you might receive both webhook and customer redirection around the same time, you should not process the order twice. This is true for most cases.

In very rare scenarios, our webhook call pertaining to order might hit your server more than once. This can happen due to network fluctuations. So, care must be taken to ensure that such scenarios are handled too.


Event NameDescriptionWebhook API version
ORDER_SUCCEEDEDGenerated when payment is successful for an orderFor all the versions
ORDER_REFUNDEDGenerated when a refund is successfulFor all the versions
ORDER_FAILEDGenerated when an order payment failsFor all the versions >= "2016-07-19"
ORDER_REFUND_FAILEDGenerated when an order refund failsFor all the versions
TXN_CREATEDGenerated when payment is initiated for an orderFor all the versions >= "2016-10-27"
REFUND_MANUAL_REVIEW_NEEDEDGenerated when the refund status is ambiguous and the refund has to be manually reconciled by the merchant with the payment processor.For all the versions
AUTO_REFUND_SUCCEEDEDGenerated when the transaction is auto refunded and refund is successFor all the versions >= "2019-11-11"
AUTO_REFUND_FAILEDGenerated when the transaction is auto refunded and refund is failureFor all the versions >= "2019-11-11"
MANDATE_CREATEDGenerated when the mandate is createdFor all the versions
MANDATE_ACTIVATEDGenerated when the mandate is successful registeredFor all the versions
MANDATE_FAILEDGenerated when the mandate registration failsFor all the versions
MANDATE_REVOKEDGenerated when the mandate is revoked by the merchantFor all the versions

Webhook API version can be configured in the dashboard -> Settings -> Webhooks Tab -> Webhook API version

Response structure for Webhook

  "id": "evt_gsu1c0r7umcfrxeb",
  "date_created": "2015-06-03T10:42:25Z",
  "event_name": "ORDER_SUCCEEDED",
  "content": {
    "order": {
        /* Complete order data as obtained from /order/status API */

Handling Failures
If your server is not reachable and we receive a non-200 response when we are attempting webhook notification, we would mark the webhook notified section in our dashboard as False.


Although our webhooks are reliable, it is always suggested that you use our GetOrderStatus API to poll our systems, in case you do not receive the webhooks in time.

This will handle the cases where the webhooks could not be notified to your system. The threshold beyond which you start polling our system can be decided basis your business use case.



 Payload : {
  "id": "evt_hk0a9ccu6qwkcryg",
  "date_created": "2018-12-05T13:53:24Z",
  "event_name": "ORDER_SUCCEEDED",
  "content": {
    "order": {
      "gateway_id": 18,
      "product_id": "",
      "auth_type": "THREE_DS",
      "customer_phone": "7598504535",
      "bank_error_message": "",
      "date_created": "2018-12-05T13:48:48Z",
      "order_id": "3e0411f7-a4b1-466b-85b2-ea55d1ba6496",
      "currency": "INR",
      "amount": 600,
      "id": "ord_c4345ba45dd3457c85fe8299f5321f53",
      "udf2": "",
      "udf1": "",
      "refunded": false,
      "udf6": "",
      "udf5": "",
      "amount_refunded": 0,
      "udf4": "",
      "txn_id": "-3e0411f7-a4b1-466b-85b2-ea55d1ba6496-1",
      "udf3": "",
      "card": {
        "saved_to_locker": false,
         "card_issuer": "",
         "card_brand": "MASTERCARD",
         "card_reference": "008485a49f2edfb9e62cefaa0cb95b13",
         "card_type": "CREDIT",
         "card_fingerprint": "1gkmbuaauq6ts9hv6v2oos4qgc",
         "expiry_year": "2023",
         "using_saved_card": true,
         "card_isin": "553227",
         "name_on_card": "",
         "last_four_digits": "",
         "expiry_month": "04"
       "merchant_id": "MID",
       "payment_method_type": "CARD",
       "payment_links": {
         "iframe": "https://sandbox.juspay.in/merchant/ipay/ord_c4345ba45dd3457c85fe8299f5321f53",
         "web": "https://sandbox.juspay.in/merchant/pay/ord_c4345ba45dd3457c85fe8299f5321f53",
         "mobile": "https://sandbox.juspay.in/merchant/pay/ord_c4345ba45dd3457c85fe8299f5321f53?mobile=true"
       "status": "CHARGED",
       "txn_uuid": "rkuk1mu7y77ltiam",
       "status_id": 21,
       "customer_email": "",
       "udf7": "",
       "udf8": "",
       "udf9": "",
       "bank_error_code": "",
       "return_url": "",
       "payment_gateway_response": {
         "rrn": "201812<HIDDEN>0010016862<HIDDEN>6345",
         "created": "2018-12-05T13:53:24Z",
         "resp_code": "01",
         "txn_id": "MID-3e0411f7-a4b1-466b-85b2-ea55d1ba6496-1",
         "epg_txn_id": "201812<HIDDEN>0010016862<HIDDEN>6345",
         "resp_message": "Txn Success",
         "auth_id_code": ""
       "payment_method": "MASTERCARD",
       "customer_id": "6989",
       "udf10": ""

Updated about a month ago


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.