Session Management
Creating FIX API Keys
Generate RSA Key Pair
First, generate a 2048 bit RSA PKCS#8 key pair:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out kalshi-fix.key
openssl rsa -in kalshi-fix.key -pubout -out kalshi-fix.pub
This creates two files:
- kalshi-fix.key: Your private key (keep secure, never share)
- kalshi-fix.pub: Your public key (share with Kalshi)
Create API Key
- Navigate to https://demo.kalshi.co/account/profile
- Click the “Create key” button
- Name your API Key
- Copy and paste the contents of
kalshi-fix.pub
into the “RSA public key” field
- Click “Create”
- Copy the generated FIX API Key (UUID format)
Store your private key securely. It is equivalent to your username + password and should never be sent to anyone, including Kalshi employees.
Logon Process
Logon Message (35=A)
The initiator must send a Logon message to establish a session. The acceptor will either:
- Respond with a Logon message acknowledging successful logon
- Respond with a Logout (35=5) message if the logon fails
Required Fields
Tag | Name | Description | Value |
---|
98 | EncryptMethod | Method of encryption | None<0> |
96 | RawData | Client logon message signature | Base64 encoded signature |
1137 | DefaultApplVerID | Default application version | FIX50SP2<9> |
Optional Fields
Tag | Name | Description | Default |
---|
8013 | CancelOrdersOnDisconnect | Cancel orders on any disconnection (including graceful logout) | N |
20126 | ListenerSession | Listen-only session (KalshiNR/RT only, requires SkipPendingExecReports=Y) | N |
20127 | ReceiveSettlementReports | Receive settlement reports (KalshiRT only) | N |
21003 | SkipPendingExecReports | Skip PENDING_{NEW|REPLACE|CANCEL} execution reports | N |
Signature Generation
The RawData field must contain a PSS RSA signature of the pre-hash string:
PreHashString = SendingTime + SOH + MsgType + SOH + MsgSeqNum + SOH + SenderCompID + SOH + TargetCompID
from base64 import b64encode
from Cryptodome.Signature import pss
from Cryptodome.Hash import SHA256
from Cryptodome.PublicKey import RSA
# Load private key
private_key = RSA.import_key(open('kalshi-fix.key').read().encode('utf-8'))
# Build message string
sending_time = "20230809-05:28:18.035"
msg_type = "A"
msg_seq_num = "1"
sender_comp_id = "your-fix-api-key-uuid"
target_comp_id = "Kalshi" # Or appropriate TargetCompID
msg_string = chr(1).join([
sending_time, msg_type, msg_seq_num,
sender_comp_id, target_comp_id
])
# Generate signature
msg_hash = SHA256.new(msg_string.encode('utf-8'))
signature = pss.new(private_key).sign(msg_hash)
raw_data_value = b64encode(signature).decode('utf-8')
Session Maintenance
Heartbeat Protocol
- Default interval: 30 seconds
- Both sides must respond to TestRequest messages
- Connection terminates if heartbeat response not received within interval
Message Sequence Numbers
Message sequence numbers reset to 0 every day during the maintenance window (2:00-2:10 AM ET).
Sequence Number Rules
- Must be unique and increase by one for each message
- Empty MsgSeqNum results in session termination
- Lower than expected = serious failure, connection terminated
- Higher than expected = recoverable with ResendRequest (if supported)
Reconnection Procedure
For unexpected sequence numbers:
- Document the issue with logs for out-of-band communication
- Check order status via REST API or UI
- Establish new session with reset sequence numbers
ResendRequest (35=2)
Only available on Order Entry with Retransmission (KalshiRT) and RFQ (KalshiRFQ) sessions.
Limitations
- Maximum 2000 messages per request
- 3-hour lookback window
- Split larger requests into chunks of 2000
Tag | Name | Description |
---|
7 | BeginSeqNo | Lower bound (inclusive) |
16 | EndSeqNo | Upper bound (inclusive) |
Error Handling
Reject (35=3)
Sent when a message cannot be processed due to session-level rule violations.
Tag | Name | Description | Required |
---|
45 | RefSeqNum | Sequence number of rejected message | Yes |
58 | Text | Human-readable error description | No |
371 | RefTagID | Tag number that caused reject | No |
372 | RefMsgType | MsgType of referenced message | No |
373 | SessionRejectReason | Rejection reason code | No |
Reject indicates serious errors that may result from faulty logic. Log and investigate these errors.
BusinessMessageReject (35=j)
Sent for business logic violations rather than session-level errors.
Tag | Name | Description | Required |
---|
45 | RefSeqNum | Sequence number of rejected message | Yes |
58 | Text | Human-readable error description | No |
371 | RefTagID | Tag number that caused reject | No |
372 | RefMsgType | MsgType of referenced message | No |
379 | BusinessRejectRefID | Business-level ID of rejected message | No |
380 | BusinessRejectReason | Business rejection reason code | Yes |
Session Termination
Logout (35=5)
Graceful session termination:
- Initiator sends Logout message
- Acceptor responds with Logout (empty Text field)
- Transport layer connection terminated
Tag | Name | Description |
---|
58 | Text | Error description (if any) |
If CancelOrdersOnDisconnect=Y
, all orders are canceled even on graceful logout.
All messages must include standard FIX headers:
Tag | Name | Description | Requirements |
---|
8 | BeginString | Protocol version | FIXT.1.1 (must be first) |
9 | BodyLength | Message length in bytes | Must be second |
34 | MsgSeqNum | Message sequence number | Unique, incrementing |
35 | MsgType | Message type | Must be third |
52 | SendingTime | UTC timestamp | Within 120 seconds of server time |
1137 | ApplVerID | Application version | Only FIX50SP2<9> is accepted |
Message Trailers
Tag | Name | Description | |
---|
10 | CheckSum | Standard FIX checksum | Must be last field |
CheckSum is calculated by summing ASCII values of all characters (except checksum field) modulo 256.
Best Practices
-
Session Configuration
- Use unique ClOrdIDs across all message types
- Implement proper heartbeat handling
- Monitor sequence numbers carefully
-
Error Recovery
- Implement automatic reconnection logic
- Store order state locally for recovery
- Use drop copy session for missed messages
-
Security
- Rotate API keys periodically
- Monitor for unauthorized access
- Use secure storage for private keys