Skip to main content

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

  1. Navigate to https://demo.kalshi.co/account/profile
  2. Click the “Create key” button
  3. Name your API Key
  4. Copy and paste the contents of kalshi-fix.pub into the “RSA public key” field
  5. Click “Create”
  6. 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

TagNameDescriptionValue
98EncryptMethodMethod of encryptionNone<0>
96RawDataClient logon message signatureBase64 encoded signature
1137DefaultApplVerIDDefault application versionFIX50SP2<9>

Optional Fields

TagNameDescriptionDefault
8013CancelOrdersOnDisconnectCancel orders on any disconnection (including graceful logout)N
20126ListenerSessionListen-only session (KalshiNR/RT only, requires SkipPendingExecReports=Y)N
20127ReceiveSettlementReportsReceive settlement reports (KalshiRT only)N
21003SkipPendingExecReportsSkip PENDING_{NEW|REPLACE|CANCEL} execution reportsN

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

  1. Must be unique and increase by one for each message
  2. Empty MsgSeqNum results in session termination
  3. Lower than expected = serious failure, connection terminated
  4. Higher than expected = recoverable with ResendRequest (if supported)

Reconnection Procedure

For unexpected sequence numbers:
  1. Document the issue with logs for out-of-band communication
  2. Check order status via REST API or UI
  3. 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
TagNameDescription
7BeginSeqNoLower bound (inclusive)
16EndSeqNoUpper bound (inclusive)

Error Handling

Reject (35=3)

Sent when a message cannot be processed due to session-level rule violations.
TagNameDescriptionRequired
45RefSeqNumSequence number of rejected messageYes
58TextHuman-readable error descriptionNo
371RefTagIDTag number that caused rejectNo
372RefMsgTypeMsgType of referenced messageNo
373SessionRejectReasonRejection reason codeNo
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.
TagNameDescriptionRequired
45RefSeqNumSequence number of rejected messageYes
58TextHuman-readable error descriptionNo
371RefTagIDTag number that caused rejectNo
372RefMsgTypeMsgType of referenced messageNo
379BusinessRejectRefIDBusiness-level ID of rejected messageNo
380BusinessRejectReasonBusiness rejection reason codeYes

Session Termination

Logout (35=5)

Graceful session termination:
  1. Initiator sends Logout message
  2. Acceptor responds with Logout (empty Text field)
  3. Transport layer connection terminated
TagNameDescription
58TextError description (if any)
If CancelOrdersOnDisconnect=Y, all orders are canceled even on graceful logout.

Message Headers

All messages must include standard FIX headers:
TagNameDescriptionRequirements
8BeginStringProtocol versionFIXT.1.1 (must be first)
9BodyLengthMessage length in bytesMust be second
34MsgSeqNumMessage sequence numberUnique, incrementing
35MsgTypeMessage typeMust be third
52SendingTimeUTC timestampWithin 120 seconds of server time
1137ApplVerIDApplication versionOnly FIX50SP2<9> is accepted

Message Trailers

TagNameDescription
10CheckSumStandard FIX checksumMust be last field
CheckSum is calculated by summing ASCII values of all characters (except checksum field) modulo 256.

Best Practices

  1. Session Configuration
    • Use unique ClOrdIDs across all message types
    • Implement proper heartbeat handling
    • Monitor sequence numbers carefully
  2. Error Recovery
    • Implement automatic reconnection logic
    • Store order state locally for recovery
    • Use drop copy session for missed messages
  3. Security
    • Rotate API keys periodically
    • Monitor for unauthorized access
    • Use secure storage for private keys
I