Webhooks

Webhook enables partners to receive push notifications to their server as and when the events like single stock (or basket) order update and holding import occur.

Overview ⚓️

Webhook is an event-driven communication method where a webhook provider (smallcase Gateway backend) delivers data to a webhook consumer (Gateway partner backend) as and when an event occurs. A more traditional way to get almost real-time data was for consumers to frequently poll the provider. However, webhooks are considered more efficient and reliable.

smallcase Gateway Webhook enables partners to receive push notifications to their server as and when the events like securities order update or holding import occur.

smallcase Gateway sends a POST request with a JSON payload to your registered postback url when

  1. securities transaction
    • order is placed
    • order is completed (all stocks in batch are in their final state)
  2. holdings import
    • user successfully completes holdings import transaction on client-side
  3. Mutual Fund Holdings Import
    • user successfully completes Mutual funds holdings import transaction on client-side
  4. pending action on invested smallcase
    • whenever there's a new action pending for user to act upon

Consuming a Webhook

  1. Create a public API endpoint in your backend which will listen for new data that would be delivered by the smallcase Gateway backend.
  2. Once the API is up & running, share the endpoint URL with the smallcase Gateway team so that we can register in our system.

Authentication

To ensure the authenticity of webhook hits, checksum-based authentication is used.

The JSON payload has a checksum key which is SHA256 hash of timestamp + smallcaseAuthId (signed with API_SECRET. For every Postback you receive, you should compute this checksum at your end and match it with the checksum in the payload.

Dino Chiesa's HMAC generator is open-sourced playground (Note: pasting secrets on a third-party website is dangerous. Take utmost caution!).

How to verify checksum at our end? Sample Code?

https://developers.gateway.smallcase.com/discuss/61f1c4341361af0039f395e5

smallcase Gateway Webhooks

smallcase Gateway offer webhooks for the following use cases -

  1. Stocks order
  2. Holdings Import
  3. Mutual Fund Holdings Import
  4. Pending user action (invested smallcase)

Stocks order

Sample payload

{
	"batchId":"63afcadb204dc65ef0c72862",
	"buyAmount":0,
	"sellAmount":0,
	"quantity":2,
	"filled":0,
	"status":"PLACED",
	"variety":"regular",
	"orders":[
		{
			"status":"PLACED",
			"quantity":1,
			"tradingsymbol":"RELIANCE",
			"transactionType":"SELL",
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"errorCode":null,
			"statusMessage":null,
			"price":0
		},
		{
			"status":"PLACED",
			"quantity":1,
			"tradingsymbol":"ADANIENT",
			"transactionType":"BUY",
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"errorCode":null,
			"statusMessage":null,
			"price":0
		}
	],
	"unplaced":[
	],
	"transactionId":"TRX_59671f981a79466fb1be4d918dd3bc6a",
	"broker":"kite-leprechaun",
	"smallcaseAuthId":"5ef33705f610f80b5453b319",
	"timestamp":"2022-12-31T05:38:37.595Z",
	"checksum":"f6609f15f0dd38eaf07c282911e7dfd69c11e03d14f814d23d2dc84627d80299"
}
{
	"batchId":"63afc98b0a485bcb6316aa93",
	"buyAmount":0,
	"sellAmount":0,
	"quantity":1,
	"filled":0,
	"status":"PLACED",
	"variety":"amo",
	"orders":[
		{
			"status":"PLACED",
			"quantity":1,
			"tradingsymbol":"INFY",
			"transactionType":"BUY",
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"errorCode":null,
			"statusMessage":null,
			"price":0
		}
	],
	"unplaced":[
	],
	"transactionId":"TRX_e8c96d4a58c54cbcb9e466dcf6b5b7cf",
	"broker":"kite-leprechaun",
	"smallcaseAuthId":"5ef33705f610f80b5453b319",
	"timestamp":"2022-12-31T05:32:59.786Z",
	"checksum":"7a31b1aaeba385c0347383bd21c2c290456ef11905939318c3355083b207bdba"
}
{
	"batchId":"63afc92e0a485bcb6316aa6c",
	"buyAmount":0,
	"sellAmount":0,
	"quantity":3,
	"filled":0,
	"status":"PLACED",
	"variety":"regular",
	"orders":[
		{
			"status":"PLACED",
			"quantity":1,
			"tradingsymbol":"INFY",
			"transactionType":"SELL",
			"exchange":"NSE",
			"orderType":"SLM",
			"product":"CNC",
			"errorCode":null,
			"statusMessage":null,
			"price":0,
			"triggerPrice":1500
		},
		{
			"status":"PLACED",
			"quantity":1,
			"tradingsymbol":"CREDITACC",
			"transactionType":"BUY",
			"exchange":"NSE",
			"orderType":"LIMIT",
			"product":"CNC",
			"errorCode":null,
			"statusMessage":null,
			"price":900
		},
		{
			"status":"PLACED",
			"quantity":1,
			"tradingsymbol":"RELIANCE",
			"transactionType":"BUY",
			"exchange":"NSE",
			"orderType":"SL",
			"product":"CNC",
			"errorCode":null,
			"statusMessage":null,
			"price":2610,
			"triggerPrice":2600
		}
	],
	"unplaced":[
	],
	"transactionId":"TRX_107f74ebb93f4a2fb304b17da095c9a1",
	"broker":"kite-leprechaun",
	"smallcaseAuthId":"5ef33705f610f80b5453b319",
	"timestamp":"2022-12-31T05:31:28.477Z",
	"checksum":"c08d83f90b5506bec4a8460d0de3c49bfe2e88cafa3ff77d6d786f4544e8e92f"
}
{
	"batchId":"63afcadb204dc65ef0c72862",
	"buyAmount":3858.25,
	"sellAmount":2547.14,
	"quantity":2,
	"filled":2,
	"status":"COMPLETED",
	"variety":"regular",
	"completedDate":"2022-12-31T05:38:40.616Z",
	"orders":[
		{
			"status":"COMPLETE",
			"quantity":1,
			"tradingsymbol":"RELIANCE",
			"transactionType":"SELL",
			"averagePrice":2547.14,
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"filledQuantity":1,
			"exchangeOrderId":"5ee87f210f059b28fee59afb63afcadbf563a6d60600140e",
			"errorCode":null,
			"statusMessage":"NA",
			"orderTimestamp":"2022-12-31T00:08:35.473Z",
			"price":0
		},
		{
			"status":"COMPLETE",
			"quantity":1,
			"tradingsymbol":"ADANIENT",
			"transactionType":"BUY",
			"averagePrice":3858.25,
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"filledQuantity":1,
			"exchangeOrderId":"5ee87f210f059b28fee59afb63afcaddf563a6d606001424",
			"errorCode":null,
			"statusMessage":"NA",
			"orderTimestamp":"2022-12-31T00:08:37.530Z",
			"price":0
		}
	],
	"unplaced":[
		
	],
	"transactionId":"TRX_59671f981a79466fb1be4d918dd3bc6a",
	"broker":"kite-leprechaun",
	"smallcaseAuthId":"5ef33705f610f80b5453b319",
	"timestamp":"2022-12-31T05:38:40.669Z",
	"checksum":"08e084d16fec3e283d5988195f36044bedd25af56bf81c6b87464c8e92ad15c1"
}
{
	"batchId":"63afc9fc0a485bcb6316aaa2",
	"buyAmount":0,
	"sellAmount":0,
	"quantity":1,
	"filled":0,
	"status":"MARKEDCOMPLETE",
	"variety":"regular",
	"completedDate":"2022-12-31T05:34:55.496Z",
	"orders":[
		{
			"status":"CANCELLED",
			"quantity":1,
			"tradingsymbol":"ZOMATO",
			"transactionType":"BUY",
			"averagePrice":59,
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"filledQuantity":0,
			"exchangeOrderId":"5ee87f210f059b28fee59afb63afc9fcf563a6d6060013ba",
			"errorCode":"exchgNotEnabled",
			"statusMessage":"This segment is not activated for your Upstox account.",
			"orderTimestamp":"2022-12-31T00:04:52.417Z",
			"price":0
		}
	],
	"unplaced":[
	],
	"transactionId":"TRX_275aaab7082b44fba6e23cdf594ab555",
	"broker":"kite-leprechaun",
	"smallcaseAuthId":"5ef33705f610f80b5453b319",
	"timestamp":"2022-12-31T05:34:55.557Z",
	"checksum":"36c83f1c9740cb2bec25440f452eeb4087fe68525ba0545b39500a66686d0d70"
}
{
	"batchId":"63afc98b0a485bcb6316aa93",
	"buyAmount":0,
	"sellAmount":0,
	"quantity":1,
	"filled":0,
	"status":"UNPLACED",
	"variety":"regular",
	"orders":[
	],
	"unplaced":[
		{
			"status":"ERROR",
			"quantity":1,
			"tradingsymbol":"SATHAISPAT",
			"transactionType":"BUY",
			"exchange":"NSE",
			"orderType":"MARKET",
			"product":"CNC",
			"errorCode":"circuitLimitExceeded",
			"statusMessage":"Exception found for order: GMK2212301301195HCLH3QCU51V This company is currently banned from the stock market.",
			"price":0
		}
	],
	"transactionId":"TRX_e8c96d4a58c54cbcb9e466dcf6b5b7cf",
	"broker":"kite-leprechaun",
	"smallcaseAuthId":"5ef33705f610f80b5453b319",
	"timestamp":"2022-12-31T05:32:59.786Z",
	"checksum":"7a31b1aaeba385c0347383bd21c2c290456ef11905939318c3355083b207bdba"
}

🚧

Batch status (status) should not be relied for determining order status

Use individual stock status (orders[].status) & filled quantity (orders[].filledQuantity) to determine the status of each security.

📘

Payload explanation

The structure is explained in the securities transaction guide here. It contains an explanation for each key in the response, along with enums. Note that the structure would vary a bit for SDK response vs webhook. The sample response above is the correct representation of the webhook structure.

Can there be multiple hits for a transactionId?
Yes. For every transaction, the webhook will be called on order placement, and thereafter on transaction's batch status update (if any).

The batch status can update for transaction with batch status in "PLACED" state (orderBatches[].status = PLACED).

For example, a user placed an AMO order, and on the next trading session the order got executed. The first webhook event would be as soon as the user confirms the order. And the next event would be fired when the order got executed / cancelled.



Holdings Import

Sample payload

{
  "smallcases": {
    "public": [
      {
        "scid": "SCAW_0001",
        "name": "All Weather Investing",
        "investmentDetailsURL": "https://smallcase.zerodha.com/details/5f2d22636678954954f29a2d?",
        "shortDescription": "Diversify with equity, gold & fixed income ETFs for recession-proof investing",
        "imageUrl": "https://assets.smallcase.com/images/smallcases/200/SCAW_0001.png",
        "stats": {
          "currentValue": 4921.89,
          "totalReturns": -36.65253999999927
        },
        "constituents": [
          {
            "ticker": "NIFTYBEES",
            "shares": 10
          },
          {
            "ticker": "JUNIORBEES",
            "shares": 3
          },
          {
            "ticker": "LIQUIDBEES",
            "shares": 1
          },
          {
            "ticker": "GOLDBEES",
            "shares": 39
          }
        ]
      }
    ],
    "private": {
      "stats": {
        "currentValue": 57.31,
        "totalReturns": 1.31
      }
    }
  },
  "securities": [
    {
      "holdings": {
        "quantity": 4,
        "averagePrice": 36.68
      },
      "positions": {
        "nse": {
          "quantity": 1,
          "averagePrice": 34.1
        },
        "bse": {
          "quantity": 1,
          "averagePrice": 34.1
        }
      },
      "transactableQuantity": 5,
      "smallcaseQuantity": 2,
      "nseTicker": "J&KBANK",
      "bseTicker": "J&KBANK",
      "isin": "INE168A01041",
      "name": "Jammu and Kashmir Bank Ltd"
    },
    {
      "holdings": {
        "quantity": 1,
        "averagePrice": 28.4
      },
      "positions": {
        "nse": {
          "quantity": 0,
          "averagePrice": 0
        },
        "bse": {
          "quantity": 0,
          "averagePrice": 0
        }
      },
      "transactableQuantity": 1,
      "smallcaseQuantity": 0,
      "nseTicker": "MASPTOP50",
      "bseTicker": "MASPTOP50",
      "isin": "INF769K01HP3",
      "name": "Mirae Asset S&P 500 Top 50 ETF"
    },
    {
      "holdings": {
        "quantity": 1,
        "averagePrice": 10.53
      },
      "positions": {
        "nse": {
          "quantity": 0,
          "averagePrice": 0
        },
        "bse": {
          "quantity": 0,
          "averagePrice": 0
        }
      },
      "transactableQuantity": 1,
      "smallcaseQuantity": 0,
      "nseTicker": "AXISBPSETF",
      "bseTicker": null,
      "isin": "INF846K01Z04",
      "name": "Axis AAA Bond Plus SDL ETF-2026 Matur. Reg. Growth"
    },
    {
      "holdings": {
        "quantity": 1,
        "averagePrice": 9.8
      },
      "positions": {
        "nse": {
          "quantity": 0,
          "averagePrice": 0
        },
        "bse": {
          "quantity": 0,
          "averagePrice": 0
        }
      },
      "transactableQuantity": 1,
      "smallcaseQuantity": 0,
      "nseTicker": "SUZLON",
      "bseTicker": "SUZLON",
      "isin": "INE040H01021",
      "name": "Suzlon Energy Ltd"
    },
    {
      "holdings": {
        "quantity": 1,
        "averagePrice": 48.5
      },
      "positions": {
        "nse": {
          "quantity": 0,
          "averagePrice": 0
        },
        "bse": {
          "quantity": 0,
          "averagePrice": 0
        }
      },
      "transactableQuantity": 1,
      "smallcaseQuantity": 0,
      "nseTicker": "ICICIB22",
      "bseTicker": "ICICIB22",
      "isin": "INF109KB15Y7",
      "name": "Bharat 22 ETF"
    },
    {
      "holdings": {
        "quantity": 1,
        "averagePrice": 36.05
      },
      "positions": {
        "nse": {
          "quantity": 0,
          "averagePrice": 0
        },
        "bse": {
          "quantity": 0,
          "averagePrice": 0
        }
      },
      "transactableQuantity": 1,
      "smallcaseQuantity": 0,
      "nseTicker": "PNB",
      "bseTicker": "PNB",
      "isin": "INE160A01022",
      "name": "Punjab National Bank"
    }
  ],
  "updating": false,
  "lastUpdate": "2022-03-24T11:33:52.986Z",
  "snapshotDate": "2022-03-24T11:24:00.871Z",
  "notes": "your-notes-goes-here",
  "smallcaseAuthId": "6195e288360acf9ebc060d23",
  "broker": "groww",
  "transactionId": "TRX_06726c28769e44b8920bd6eb5bbeef17",
  "timestamp": "2022-03-24T11:33:53.091Z",
  "checksum": "fe7a17d4f82831918a1363af080f5a92f382efa872b1a2a7823964b6bf7e3285"
}
{
  "smallcases": {
    "public": [
      {
        "scid": "SCAW_0001",
        "name": "All Weather Investing",
        "investmentDetailsURL": "https://smallcase.zerodha.com/details/5f7ec65caf9e9e3ce695bc4a?",
        "shortDescription": "Diversify with equity, gold & fixed income ETFs for recession-proof investing",
        "imageUrl": "https://assets.smallcase.com/images/smallcases/200/SCAW_0001.png",
        "stats": {
          "currentValue": 4308.15,
          "totalReturns": 727.0412199999994
        },
        "constituents": [
          {
            "ticker": "GOLDBEES",
            "shares": 23
          },
          {
            "ticker": "JUNIORBEES",
            "shares": 3
          },
          {
            "ticker": "NIFTYBEES",
            "shares": 6
          },
          {
            "ticker": "LIQUIDBEES",
            "shares": 1
          }
        ]
      },
      {
        "scid": "SCNM_0010",
        "name": "The Great Indian Middle Class - Custom",
        "investmentDetailsURL": "https://smallcase.zerodha.com/details/60d2bbb999a5de24eba97ab4?",
        "shortDescription": "Companies focused on the growing middle class. Running India's consumption engine",
        "imageUrl": "https://assets.smallcase.com/images/smallcases/200/SCNM_0010.png",
        "stats": {
          "currentValue": 0,
          "totalReturns": 0
        },
        "constituents": []
      }
    ],
    "private": []
  },
  "securities": {
    "holdings": [
      {
        "ticker": "INFY",
        "shares": 14,
        "name": "Infosys Ltd",
        "exchange": "NSE",
        "averagePrice": 1474.2927272727275
      },
      {
        "ticker": "MRF",
        "shares": 10,
        "name": "MRF Ltd",
        "exchange": "NSE",
        "averagePrice": 66105.91
      },
      {
        "ticker": "ADROITINFO",
        "shares": 2,
        "name": "Adroit Infotech Ltd",
        "exchange": "NSE",
        "averagePrice": 7
      },
      {
        "ticker": "DRREDDY",
        "shares": 14,
        "name": "Dr Reddy's Laboratories Ltd",
        "exchange": "NSE",
        "averagePrice": 4097.744285714286
      },
      {
        "ticker": "ITC",
        "shares": 29,
        "name": "ITC Ltd",
        "exchange": "NSE",
        "averagePrice": 200.40530153724868
      },
      {
        "ticker": "ASIANPAINT",
        "shares": 7,
        "name": "Asian Paints Ltd",
        "exchange": "NSE",
        "averagePrice": 2118.8002857142856
      }
    ]
  },
  "updating": false,
  "lastUpdate": "2021-12-20T09:27:03.014Z",
  "snapshotDate": "2021-12-20T09:27:03.014Z",
  "smallcaseAuthId": "5ef33705f610f80b5453b319",
  "broker": "iifl",
  "notes": "your-notes-goes-here",
  "transactionId": "TRX_7c30e4d9904f4f79b692f281a83cdc1f",
  "timestamp": "2021-12-20T09:27:03.059Z",
  "checksum": "d04a000b47c44bf6a721c829b8a8815b8470b2b5c767112b92f82943ea712fdd"
}

Continue reading: Holdings Import v2 📃 | Guide to Holdings Import integration.



Mutual Fund Holdings Import

Sample payload

Refer here

Note on webhook

Webhook authentication slightly varies for MF Holdings Import. As described in the Authentication, the JSON payload has a checksum key which is SHA256 hash of timestamp + smallcaseAuthId

For the Mutual funds holdings Import webhook response, the JSON payload has a checksum key which is SHA256 hash of timestamp + transactionId (instead of smallcaseAuthId)



Pending user action (smallcase orders)

A user invested in a smallcase can have pending actions to be performed. These can be -

  • FIX ACTION : This triggers a repair order on smallcase. In a repair order, all the unfilled stocks of the previous transaction are attempted again as a fresh order on the same invested smallcase.

  • REBALANCE ACTION : smallcases are reviewed & updated on a regular basis. Once a rebalance update is pushed, a rebalance order is added to the invested smallcase.

  • SIP ACTION : A user can set SIP on an invested smallcase. On the due date, a SIP order is added to the invested smallcase.

Learn more about pending actions? Check this guide!

Partners can use this pending actions data to communicate the same to their users. Everyday smallcase Gateway will hit your pending action webhook for all relevant gateway users (one hit per user). The pending action stays till the user acts upon the action. You will receive the same action everyday until user acts upon it, or new action of same type is added.

{
    "eventType": "USER.PENDING_ACTIONS",
    "timestamp": "2021-06-01T09:28:14.561Z",
    "checksum": "5a60d322fd0c1fcb51bf3e272c18d94016e5a550ec1fad4189ef221034b11175",
    "data": {
        "smallcaseAuthId": "5e96ebd5e5520425f7433610",
        "investments": [
            {
                "iscid": "5e972a5fbd687e4da5f00c47",
                "smallcaseName": "Magic Formula",
                "scid": "SCMO_0006",
                "fixAction": {
                    "date": "2020-04-16T06:51:37.240Z",
                    "filled": 7,
                    "quantity": 11,
                    "originalLabel": "BUY"
                }
            },
            {
                "iscid": "5e971fdfbd687e4da5f00c3f",
                "smallcaseName": "Low Risk - Smart Beta",
                "scid": "SCSB_0003",
                "rebalanceAction": {
                    "label": "Quarterly Update: Weights Rebalance",
                    "rationale": "There are no changes in the stocks or weighting scheme of this smallcase for this quarter. The weights in which you hold stocks might have changed due to changing prices, rebalance this smallcase to move back to the prescribed weights",
                    "date": "2020-06-18T00:00:00.000Z"
                }
            },
            {
                "iscid": "5e980b61dba2e16cf3e6d04d",
                "smallcaseName": "Quantum",
                "scid": "GATEMO_0001",
                "sipAction": {
                    "frequency":"1m",
                    "amount":85.64,
                    "scheduledDate":"2021-04-27T00:00:00.000Z",
                    "type": "MANUAL"
                }
            }
        ]
    }
}

FAQs

  1. Is there any retry mechanism, in case our webhook could not capture data on the first try?
    Yes, we have a retry mechanism in place which would retry hitting the webhook URL after a certain duration -
    i. First retry after 15 minutes
    ii. Second retry after 24 hours
    iii. Final retry after 48 hours

  2. There's no Webhook support for our use case. Is it possible to add support?
    We are always open to exploring use cases. Let us know your use case for which you need webhook support, we will definitely see what can be done.