The Kite Connect websocket sends you live prices with one connection and three callbacks. By the end of this guide you'll have a short Python script that subscribes to two instruments, prints each tick, reconnects when the network drops and shuts down without leaving a connection hanging. You can subscribe to up to 3000 instruments per connection, and one API key can have 3 connections.
This guide only reads prices. It sends no orders. If you want that side, placing your first order is the companion piece.
Before you start
| You need | Why |
|---|---|
| The Connect plan | Live prices over the websocket are part of the paid plan. What Kite Connect costs has the details. |
| Today's access token | The websocket needs a valid one. Zerodha staff on the developer forum say a 403 means the api_key or access token is invalid. Refreshing the token covers the daily login. |
| Instrument tokens | The websocket subscribes by numeric instrument token, not by symbol. |
pip install kiteconnect
The websocket client in that library is called KiteTicker. The methods and defaults below come from the pykiteconnect documentation and the websocket documentation.
The three modes
Every subscribed instrument streams in one of three modes. Each mode sends a packet of a different size:
| Mode | Constant in the library | What the docs say it carries | Packet size |
|---|---|---|---|
ltp |
MODE_LTP |
Last traded price only | 8 bytes |
quote |
MODE_QUOTE |
Several fields, excluding market depth | 44 bytes |
full |
MODE_FULL |
Several fields, including market depth | 184 bytes |
I use the smallest mode that has the fields I need. If all you want is a price to draw on a screen, ltp is enough. If you want volume, open, high, low and close, that's quote. Depth needs full.
In pykiteconnect 4.2.0, subscribe() records each token in MODE_QUOTE unless you call set_mode afterwards. The script below still sets a mode explicitly so the choice is visible in the code.
The script
The script reads your key and token from the KITE_API_KEY and KITE_ACCESS_TOKEN environment variables, so they never sit in the file. The instrument tokens below (738561 and 5633) are the example tokens from the pykiteconnect documentation. Swap in your own. Look tokens up from the instruments list rather than typing them from memory, because a wrong token just streams the wrong instrument.
import logging
import os
import time
from kiteconnect import KiteTicker
logging.basicConfig(level=logging.INFO)
API_KEY = os.environ["KITE_API_KEY"]
ACCESS_TOKEN = os.environ["KITE_ACCESS_TOKEN"] # today's token
TOKENS = [738561, 5633] # example tokens from the docs
kws = KiteTicker(API_KEY, ACCESS_TOKEN) # reconnect=True by default
def on_connect(ws, response):
# Runs on the first connect and again after every reconnect.
logging.info("connected: %s", response)
ws.subscribe(TOKENS)
ws.set_mode(ws.MODE_QUOTE, TOKENS)
def on_ticks(ws, ticks):
# Keep this fast. Hand the data to a queue; don't calculate here.
for t in ticks:
print(t["instrument_token"], t["last_price"], t.get("exchange_timestamp"))
def on_close(ws, code, reason):
logging.info("closed: %s %s", code, reason)
def on_error(ws, code, reason):
logging.error("error: %s %s", code, reason)
def on_reconnect(ws, attempts_count):
logging.warning("reconnecting, attempt %s", attempts_count)
def on_noreconnect(ws):
logging.error("gave up reconnecting")
kws.on_connect = on_connect
kws.on_ticks = on_ticks
kws.on_close = on_close
kws.on_error = on_error
kws.on_reconnect = on_reconnect
kws.on_noreconnect = on_noreconnect
kws.connect(threaded=True) # event loop runs in a background thread
try:
while True:
time.sleep(1) # main thread stays free; put your own logic here
except KeyboardInterrupt:
pass
finally:
kws.stop_retry() # stop any reconnect attempts first
kws.close()
logging.info("shut down")
I have not run this against a live connection while writing the guide; it follows the documented method names and signatures, so test it in market hours on a small subscription first.
A few choices worth explaining:
- Subscribe inside
on_connect. The docs' own example does this. It runs once the connection is up; after a reconnect the library's ownresubscribe()also re-sends the stored tokens. connect(threaded=True). By the docs,connect()runs the event loop on the main thread unless you passthreaded=True. Threaded mode leaves your main thread free for other work and for catching Ctrl-C.- Don't call
ws.stop()inon_errororon_close.stop()stops the event loop and prevents reconnection. A forum answer on error 1006 makes the same point: removews.stop()from error handlers if you want automatic reconnection.
Reading a tick
Each call to on_ticks gets a list of tick dictionaries, one per instrument that updated. Here is an illustrative, made-up tick in full mode. The numbers are invented and don't describe any real market.
{
"tradable": True,
"mode": "full",
"instrument_token": 738561,
"last_price": 100.0,
"last_traded_quantity": 10,
"average_traded_price": 99.5,
"volume_traded": 123456,
"total_buy_quantity": 5000,
"total_sell_quantity": 4000,
"ohlc": {"open": 99.0, "high": 101.0, "low": 98.5, "close": 99.8},
"change": 0.2,
"last_trade_time": datetime.datetime(2026, 9, 30, 11, 15, 2),
"exchange_timestamp": datetime.datetime(2026, 9, 30, 11, 15, 3),
"oi": 0,
"oi_day_high": 0,
"oi_day_low": 0,
"depth": {"buy": [...5 entries...], "sell": [...5 entries...]},
}
The key names in the pykiteconnect documentation, and what they mean:
| Key | Meaning |
|---|---|
instrument_token |
Which instrument this tick is for |
tradable |
Whether the instrument is tradable |
mode |
The mode this tick arrived in |
last_price |
Last traded price |
last_traded_quantity |
Quantity in the last trade |
average_traded_price |
Average traded price for the day |
volume_traded |
Day volume |
total_buy_quantity, total_sell_quantity |
Aggregate quantity waiting on each side |
ohlc |
Open, high, low and close as a nested dictionary |
change |
Price change, as a percentage |
last_trade_time, exchange_timestamp |
Timestamps, as datetime objects |
oi, oi_day_high, oi_day_low |
Open interest and its day high and low, for derivatives |
depth |
Five best bids and five best offers, in full mode |
Two practical notes. First, not every key appears in every mode: an ltp tick is much smaller than a full one, so read optional fields with .get() as the script does. Second, the docs say index packets (NIFTY 50, SENSEX) have a different structure from tradable instruments, with fewer fields. If you subscribe to an index and a stock together, don't assume both ticks carry the same keys.
What the wire looks like
You don't need this to use the library, but it explains the sizes above. The websocket docs describe each binary message as one or more quote packets, and the first two bytes are the number of packets in the message. Prices arrive as integers: the docs say to divide by 100 for everything except currencies, which are divided by 10000000. The library does the parsing for you. Zerodha also sends a 1-byte heartbeat every couple of seconds when there is no data to stream, so a quiet instrument is not a dead connection.
Reconnects: what happens when the network drops
The constructor's defaults, from the pykiteconnect docs:
| Setting | Default | Meaning |
|---|---|---|
reconnect |
True |
Auto-reconnect on |
reconnect_max_tries |
50 |
Maximum reconnect attempts (capped at 300) |
reconnect_max_delay |
60 |
Maximum delay between attempts, in seconds (minimum accepted value is 5) |
connect_timeout |
30 |
Connection timeout, in seconds |
The docs say reconnection uses exponential backoff, starting at 2 seconds and rising to reconnect_max_delay. The docs list the callbacks involved: on_close when the connection closes, on_reconnect(ws, attempts_count) during reconnection attempts, and on_noreconnect when attempts exceed the maximum. The library resubscribes for you. In the pykiteconnect 4.2.0 source, subscribe() and set_mode() store every token and its mode in subscribed_tokens, and on every reconnect (not the first connect) the library's _on_open handler calls resubscribe(), which re-sends all stored tokens grouped by mode. You don't need to write your own resubscribe logic.
Subscribing inside on_connect, as the script does, works: it runs on the first connect and the library's resubscribe covers later reconnects. Because on_connect can also fire again after a reconnect, the script's subscribe call may be sent a second time. Tokens are stored in a dictionary keyed by token, so the library's own record has no duplicates. I have not tested how the server treats a repeated subscribe message. If ticks go quiet after a reconnect, a forum thread on that problem has Zerodha staff asking to see the user's websocket code, so log what your callbacks receive.
Shutting down cleanly
stop() stops the event loop and prevents reconnection. stop_retry() stops ongoing reconnect attempts. close() closes the connection. On the developer forum, a Zerodha reply about closing the ticker suggested calling stop_retry() before close(), and the same thread reports that calling close() alone caused error 1006 for some users. The script does it in that order.
As for market hours, I found nothing in the docs about the connection closing itself at the end of the session. A staff reply on the forum recommended keeping your own map of exchange timings and acting on that. So decide in your own code when to stop: after the session you trade ends, and on days with no session (check the exchange's holiday list). Outside those hours the connection can be open with nothing to send. What arrives before 9:15 is covered in the pre-open session guide.
Common mistakes
Each of these is backed by the docs or a Zerodha staff reply on the forum.
- An expired access token. Error 1006 with a 403 means the api_key or access token is invalid. Log in again each morning.
- Slow work inside
on_ticks. Staff said the streaming connection will be closed if you block theon_ticksmethod with calculations. Push ticks onto a queue and process them elsewhere. ws.stop()in error handlers. It ends reconnection for good.- Writing your own resubscribe logic. The library already re-sends stored subscriptions on each reconnect. Subscribe once in
on_connectand let it do the rest. - Going past the limits. 3000 instruments per connection and 3 connections per API key. Split large lists across connections rather than adding more to one.
- Trusting the price as a fill price. A last traded price is not a price you can necessarily trade at. That gap is the subject of paper trading vs live trading.
What I'd log while you test
Print a count of ticks per minute per instrument and the gap between exchange_timestamp and your own clock. If either looks wrong, you'll see it long before a bad number reaches anything that matters. I haven't published measurements of my own here, because they'd depend on your network and your subscription.
Sources
- Kite Connect docs: WebSocket streaming for the endpoint, the 3000 instruments and 3 connections limits, the three modes and packet sizes, the binary format, the price divisors and the heartbeat.
- pykiteconnect v4 documentation: KiteTicker for the constructor defaults, callbacks, methods, mode constants and the tick fields.
- Kite Connect forum: how to find market close and gracefully close KiteTicker (staff reply, forum).
- Kite Connect forum: error 1006, connection was closed uncleanly (staff replies, forum).
- Kite Connect forum: websocket ticks stop after disconnection and reconnection (staff replies, forum).
This guide explains how a broker API works, for information only. It is not investment advice or a recommendation to buy or sell any security. I am not registered with SEBI as an investment adviser or research analyst.