How to Build a Kick Chat Bot with Python (2026 Kick API Guide)
How to create a Kick chat bot? If you stream on Kick and want to automate chat interactions — greeting new viewers, running custom commands, moderating spam, or tracking subscriptions — you need a Kick chat bot. Unlike Twitch, where dozens of mature bot frameworks exist, the Kick ecosystem is still early. Most streamers rely on third-party tools with limited customization. Building your own Kick chat bot with Python and the official Kick API gives you full control over every interaction in your channel.
This guide walks you through every step to build a fully functional Kick chat bot from scratch — registering your app on the Kick Developer Portal, authenticating with OAuth 2.1 PKCE, sending and receiving chat messages, building a command system, adding moderation actions, listening to real-time events with EventSub webhooks, and deploying your bot to a VPS so it runs 24/7. If you want to skip the development work and automate content distribution across Kick, YouTube, TikTok, and Instagram instead, Repostit.io handles cross-platform posting automatically.
What Can a Kick Chat Bot Do?
Before writing any code, it helps to understand the full range of actions your Kick chat bot can perform through the official API. The Kick Developer API exposes endpoints for chat messaging, moderation, channel management, and real-time event subscriptions. Here is what a properly scoped Kick chat bot can handle:
- Send chat messages: Post messages to any channel where the bot is authorized, including replies to specific messages.
- Custom commands: Respond to viewer commands like
!socials,!uptime,!rank, or any custom trigger you define. - Auto-moderation: Detect spam, excessive caps, banned words, or link posting and automatically timeout or ban offenders.
- Ban and timeout users: Issue permanent bans or timed timeouts (1 minute to 7 days) directly from the bot.
- Delete messages: Remove individual chat messages that violate your channel rules.
- Welcome new followers: Listen for follow events via EventSub and greet new followers in chat automatically.
- Track subscriptions and gifts: React to subscription events, gift subs, and KICKs donations in real time.
- Update stream metadata: Change your stream title, category, and tags programmatically without opening the dashboard.
Every one of these features uses the official Kick API. No browser automation, no unofficial websocket sniffing, no reverse-engineered endpoints. Your Kick chat bot stays compliant with Kick’s Terms of Service and will not get your channel banned.
Prerequisites for Building a Kick Chat Bot
Before you can build your Kick chat bot, you need a few things set up. This is a one-time process that takes about 10 minutes.
Step 1: Create a Kick Bot Account
Do not run your Kick chat bot from your main streamer account. Create a separate Kick account specifically for the bot — something like YourChannelBot. This keeps your bot’s messages visually distinct in chat and prevents any accidental actions on your main account. The bot account needs to be a moderator in your channel to use moderation endpoints.
Step 2: Register an App on the Kick Developer Portal
- Go to the Kick Developer Portal and log in with your bot account (or your main account — the app registration is separate from the bot identity).
- Click Create Application.
- Fill in the application name (e.g.,
my-kick-chatbot), description, and set the redirect URI tohttp://localhost:3000/callbackfor local development. - After creation, copy the Client ID and Client Secret. Store these securely — you will need them for every OAuth flow.
Step 3: Choose the Right Kick Chat Bot Scopes
Scopes define what your Kick chat bot is allowed to do. Request only what you need — users are more likely to approve a bot with focused permissions. For a full-featured chat bot, these are the scopes you will need:
| Scope | Why Your Kick Chat Bot Needs It |
|---|---|
chat:write |
Send messages and command responses in chat |
user:read |
Read the bot’s own user ID and profile info |
channel:read |
Read channel info (stream title, category, live status) |
moderation:ban |
Ban, unban, and timeout users from chat |
moderation:chat_message:manage |
Delete individual chat messages |
events:subscribe |
Subscribe to real-time events (follows, subs, chat messages) |
For a Kick chat bot that only sends messages and responds to commands — without moderation features — you only need chat:write, user:read, and events:subscribe. For a detailed breakdown of every available scope, see our Kick API Guide.
Step 4: Install Python Dependencies
pip install requests flask
We use requests for API calls and flask as a lightweight local callback server to capture the OAuth authorization code. All code in this guide targets Python 3.10+.
Kick Chat Bot OAuth 2.1 Authentication with PKCE
The Kick API uses OAuth 2.1 with PKCE (Proof Key for Code Exchange) for all user-level authentication. Your Kick chat bot must complete this flow once to get an access token, then refresh it automatically on expiry. Here is the complete authentication script with a local callback server:
#!/usr/bin/env python3
"""
Kick Chat Bot — OAuth 2.1 PKCE Authentication
Authenticates with the Kick API and saves tokens for reuse.
"""
import os
import json
import hashlib
import base64
import secrets
import webbrowser
import threading
import time
import requests
from flask import Flask, request as flask_request
# ─── Configuration ───────────────────────────────────────────────
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
REDIRECT_URI = "http://localhost:3000/callback"
SCOPES = "chat:write user:read channel:read moderation:ban moderation:chat_message:manage events:subscribe"
TOKEN_FILE = "kick_tokens.json"
# ─── PKCE Generation ────────────────────────────────────────────
code_verifier = secrets.token_urlsafe(64)
code_challenge = (
base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
.decode()
.rstrip("=")
)
state = secrets.token_urlsafe(16)
# ─── Token Persistence ──────────────────────────────────────────
def save_tokens(tokens):
"""Save tokens to disk for reuse between bot restarts."""
with open(TOKEN_FILE, "w") as f:
json.dump(tokens, f, indent=2)
print("[AUTH] Tokens saved to", TOKEN_FILE)
def load_tokens():
"""Load previously saved tokens."""
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE, "r") as f:
return json.load(f)
return None
def refresh_access_token(refresh_token):
"""Use refresh token to get a new access token without user interaction."""
response = requests.post(
"https://id.kick.com/oauth/token",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"refresh_token": refresh_token,
},
)
if response.status_code == 200:
tokens = response.json()
save_tokens(tokens)
print("[AUTH] Token refreshed successfully")
return tokens
else:
print(f"[AUTH ERROR] Refresh failed: {response.status_code} {response.text}")
return None
# ─── OAuth Flow with Local Callback Server ──────────────────────
auth_code_result = {"code": None}
app = Flask(__name__)
@app.route("/callback")
def callback():
"""Capture the authorization code from Kick's redirect."""
received_state = flask_request.args.get("state")
if received_state != state:
return "State mismatch — possible CSRF attack.", 400
auth_code_result["code"] = flask_request.args.get("code")
return "Authorization successful!
You can close this tab and return to the terminal.
"
def run_callback_server():
"""Run Flask server in a background thread to catch the OAuth redirect."""
app.run(port=3000, debug=False, use_reloader=False)
def authenticate():
"""
Full OAuth 2.1 PKCE flow for the Kick chat bot.
Checks for existing tokens first, refreshes if expired,
and falls back to full browser-based flow if needed.
"""
# Try loading existing tokens
tokens = load_tokens()
if tokens and "refresh_token" in tokens:
refreshed = refresh_access_token(tokens["refresh_token"])
if refreshed:
return refreshed["access_token"]
# No valid tokens — run full OAuth flow
print("[AUTH] Starting OAuth 2.1 PKCE flow...")
# Start callback server in background
server_thread = threading.Thread(target=run_callback_server, daemon=True)
server_thread.start()
time.sleep(1) # Wait for server to start
# Build authorization URL
auth_url = (
f"https://id.kick.com/oauth/authorize?"
f"response_type=code&"
f"client_id={CLIENT_ID}&"
f"redirect_uri={REDIRECT_URI}&"
f"scope={SCOPES}&"
f"code_challenge={code_challenge}&"
f"code_challenge_method=S256&"
f"state={state}"
)
print("[AUTH] Opening browser for authorization...")
webbrowser.open(auth_url)
# Wait for the callback to capture the code
print("[AUTH] Waiting for authorization...")
while auth_code_result["code"] is None:
time.sleep(0.5)
auth_code = auth_code_result["code"]
print(f"[AUTH] Authorization code received")
# Exchange code for tokens
token_response = requests.post(
"https://id.kick.com/oauth/token",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"code_verifier": code_verifier,
"code": auth_code,
},
)
if token_response.status_code != 200:
print(f"[AUTH ERROR] Token exchange failed: {token_response.text}")
return None
tokens = token_response.json()
save_tokens(tokens)
print("[AUTH] Authentication complete!")
return tokens["access_token"]
This authentication module handles the complete lifecycle: it checks for saved tokens on disk, attempts a silent refresh if they exist, and only opens the browser for a full OAuth flow on the first run or if the refresh token has been revoked. This is what makes your Kick chat bot work unattended after the initial setup — critical for 24/7 deployment on a VPS.
Sending Chat Messages with Your Kick Chat Bot
Once authenticated, your Kick chat bot can send messages to any channel where it has been authorized. The /public/v1/chat endpoint accepts a broadcaster user ID, the message content, and a type field that distinguishes between regular user messages and bot messages. Here is the core message-sending function:
"""
Kick Chat Bot — Core messaging functions.
Send messages, reply to users, and post announcements.
"""
API_BASE = "https://api.kick.com"
def send_message(access_token, broadcaster_id, content, msg_type="bot"):
"""
Send a chat message to a Kick channel.
Args:
access_token: OAuth bearer token
broadcaster_id: The channel's broadcaster user ID
content: Message text (max 500 characters)
msg_type: "user" or "bot" — bot messages display with a BOT badge
Returns:
message_id if successful, None otherwise
"""
response = requests.post(
f"{API_BASE}/public/v1/chat",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"broadcaster_user_id": broadcaster_id,
"content": content,
"type": msg_type,
},
)
if response.status_code == 200:
data = response.json()
if data["data"]["is_sent"]:
print(f"[CHAT] Sent: {content}")
return data["data"]["message_id"]
print(f"[CHAT ERROR] {response.status_code}: {response.text}")
return None
def reply_to_message(access_token, broadcaster_id, content, reply_to_id, msg_type="bot"):
"""
Reply to a specific chat message. The reply appears as a threaded response
in the Kick chat UI with a visual link to the original message.
"""
response = requests.post(
f"{API_BASE}/public/v1/chat",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"broadcaster_user_id": broadcaster_id,
"content": content,
"type": msg_type,
"reply_to_message_id": reply_to_id,
},
)
if response.status_code == 200:
print(f"[CHAT] Reply sent: {content}")
return response.json()["data"]["message_id"]
print(f"[CHAT ERROR] {response.status_code}: {response.text}")
return None
def get_channel_info(access_token, channel_slug):
"""
Get channel information by slug. Returns the broadcaster_user_id
which is required for all chat and moderation API calls.
"""
response = requests.get(
f"{API_BASE}/public/v1/channels",
headers={"Authorization": f"Bearer {access_token}"},
params={"slug": channel_slug},
)
if response.status_code == 200:
data = response.json()["data"]
if data:
channel = data[0]
print(f"[CHANNEL] {channel['slug']} — Broadcaster ID: {channel['broadcaster_user_id']}")
return channel
print(f"[CHANNEL ERROR] {response.status_code}: {response.text}")
return None
Notice the type field set to "bot". When your Kick chat bot sends messages with this type, Kick displays a BOT badge next to the username in chat. This makes it clear to viewers which messages come from automation versus a real person. If you set the type to "user", the message appears as a normal chat message from the bot account without the badge.
Building a Kick Chat Bot Command System
The most useful feature of any Kick chat bot is a command system — viewers type !command in chat, and the bot responds. The command system needs to parse incoming messages, check if they start with your command prefix, match against registered commands, and execute the appropriate handler. Here is a clean, extensible command framework:
"""
Kick Chat Bot — Command system.
Register commands with decorators and handle incoming chat messages.
"""
import time
from datetime import datetime, timezone
class KickChatBot:
"""
Main Kick chat bot class with command registration and message handling.
"""
def __init__(self, access_token, broadcaster_id, prefix="!"):
self.access_token = access_token
self.broadcaster_id = broadcaster_id
self.prefix = prefix
self.commands = {}
self.cooldowns = {} # Prevent command spam
self.start_time = datetime.now(timezone.utc)
# Register built-in commands
self._register_defaults()
def command(self, name, cooldown=5, mod_only=False):
"""
Decorator to register a chat command.
Usage:
@bot.command("hello", cooldown=3)
def hello_cmd(username, args):
return f"Hey {username}! Welcome to the stream!"
"""
def decorator(func):
self.commands[name.lower()] = {
"handler": func,
"cooldown": cooldown,
"mod_only": mod_only,
"last_used": 0,
}
return func
return decorator
def _register_defaults(self):
"""Register built-in commands that every Kick chat bot should have."""
@self.command("ping", cooldown=5)
def ping_cmd(username, args):
return "Pong! Bot is online and responding."
@self.command("uptime", cooldown=10)
def uptime_cmd(username, args):
delta = datetime.now(timezone.utc) - self.start_time
hours, remainder = divmod(int(delta.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
return f"Bot has been running for {hours}h {minutes}m {seconds}s"
@self.command("commands", cooldown=10)
def commands_cmd(username, args):
cmd_list = ", ".join(f"{self.prefix}{name}" for name in sorted(self.commands.keys()))
return f"Available commands: {cmd_list}"
@self.command("socials", cooldown=10)
def socials_cmd(username, args):
return "Follow on all platforms! YouTube: /c/YourChannel | TikTok: @YourHandle | Instagram: @YourHandle"
def handle_message(self, username, message_text, message_id, is_moderator=False):
"""
Process an incoming chat message. If it starts with the command prefix,
attempt to execute the matching command.
Returns:
Response string if a command was triggered, None otherwise.
"""
if not message_text.startswith(self.prefix):
return None
parts = message_text[len(self.prefix):].strip().split(maxsplit=1)
if not parts:
return None
cmd_name = parts[0].lower()
args = parts[1] if len(parts) > 1 else ""
if cmd_name not in self.commands:
return None
cmd = self.commands[cmd_name]
# Check mod-only restriction
if cmd["mod_only"] and not is_moderator:
return None
# Check cooldown
now = time.time()
if now - cmd["last_used"] < cmd["cooldown"]:
remaining = int(cmd["cooldown"] - (now - cmd["last_used"]))
return f"Command on cooldown — try again in {remaining}s"
# Execute command
cmd["last_used"] = now
try:
response = cmd["handler"](username, args)
print(f"[CMD] {username} used !{cmd_name} → {response}")
return response
except Exception as e:
print(f"[CMD ERROR] !{cmd_name} failed: {e}")
return f"Something went wrong with !{cmd_name}"
This command system uses a decorator pattern — you register new commands with @bot.command("name") and the framework handles cooldowns, moderator-only restrictions, and argument parsing automatically. Adding a new command to your Kick chat bot is a single function definition:
# Adding custom commands to your Kick chat bot
@bot.command("dice", cooldown=3)
def dice_cmd(username, args):
import random
result = random.randint(1, 6)
return f"🎲 {username} rolled a {result}!"
@bot.command("8ball", cooldown=5)
def eightball_cmd(username, args):
import random
responses = [
"Yes, definitely!", "No way.", "Ask again later.",
"Without a doubt.", "Don't count on it.", "It is certain.",
"Very doubtful.", "Signs point to yes.", "My sources say no.",
]
return f"🎱 {random.choice(responses)}"
@bot.command("followage", cooldown=10)
def followage_cmd(username, args):
# In production, query the Kick API for actual follow date
return f"{username}, you've been following since the beginning! 💚"
@bot.command("title", cooldown=5, mod_only=True)
def title_cmd(username, args):
"""Mod-only command to update stream title."""
if not args:
return "Usage: !title "
# Calls PATCH /public/v1/channels — covered in the moderation section
return f"Stream title updated to: {args}"
Kick Chat Bot Moderation: Bans, Timeouts, and Message Deletion
A Kick chat bot without moderation capabilities is incomplete. The Kick API provides endpoints for banning users, issuing timeouts, and deleting individual messages. These are the most critical functions for keeping your chat clean during a live stream. Here is the moderation module:
"""
Kick Chat Bot — Moderation functions.
Ban, timeout, unban users and delete chat messages.
"""
def ban_user(access_token, broadcaster_id, user_id, reason="Banned by bot"):
"""
Permanently ban a user from the channel's chat.
Requires the moderation:ban scope.
"""
response = requests.post(
f"{API_BASE}/public/v1/moderation/bans",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"broadcaster_user_id": broadcaster_id,
"user_id": user_id,
"reason": reason,
},
)
if response.status_code in (200, 204):
print(f"[MOD] Banned user {user_id}: {reason}")
return True
print(f"[MOD ERROR] Ban failed: {response.status_code} {response.text}")
return False
def timeout_user(access_token, broadcaster_id, user_id, duration_minutes=10, reason="Timed out by bot"):
"""
Timeout a user for a specified duration.
Duration must be between 1 and 10080 minutes (7 days maximum).
"""
duration = max(1, min(duration_minutes, 10080))
response = requests.post(
f"{API_BASE}/public/v1/moderation/bans",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"broadcaster_user_id": broadcaster_id,
"user_id": user_id,
"duration": duration,
"reason": reason,
},
)
if response.status_code in (200, 204):
print(f"[MOD] Timed out user {user_id} for {duration}m: {reason}")
return True
print(f"[MOD ERROR] Timeout failed: {response.status_code} {response.text}")
return False
def unban_user(access_token, broadcaster_id, user_id):
"""Remove a ban or timeout from a user."""
response = requests.delete(
f"{API_BASE}/public/v1/moderation/bans",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"broadcaster_user_id": broadcaster_id,
"user_id": user_id,
},
)
if response.status_code in (200, 204):
print(f"[MOD] Unbanned user {user_id}")
return True
print(f"[MOD ERROR] Unban failed: {response.status_code} {response.text}")
return False
def delete_message(access_token, message_id):
"""
Delete a specific chat message by its ID.
Requires the moderation:chat_message:manage scope.
"""
response = requests.delete(
f"{API_BASE}/public/v1/chat/{message_id}",
headers={"Authorization": f"Bearer {access_token}"},
)
if response.status_code in (200, 204):
print(f"[MOD] Deleted message {message_id}")
return True
print(f"[MOD ERROR] Delete failed: {response.status_code} {response.text}")
return False
With these moderation functions, your Kick chat bot can enforce channel rules programmatically. You can combine them with the command system to create mod-only commands like !ban @username reason, !timeout @username 30, or !unban @username. You can also build an auto-moderation filter that scans every message and automatically times out users who post links, excessive caps, or banned phrases.
Auto-Moderation Filter for Your Kick Chat Bot
Manual moderation does not scale. When your stream grows past 100 concurrent viewers, you need automated filtering. Here is an auto-moderation module that checks every incoming message against configurable rules and takes action automatically:
"""
Kick Chat Bot — Auto-moderation filter.
Scans messages for spam, banned words, excessive caps, and links.
"""
import re
class AutoModerator:
"""
Configurable auto-moderation system for a Kick chat bot.
Checks messages against rules and returns the appropriate action.
"""
def __init__(self):
self.banned_words = [
"spam_phrase_1", "spam_phrase_2", "offensive_word",
]
self.banned_patterns = [
re.compile(r"https?://\S+", re.IGNORECASE), # Links
re.compile(r"(.)\1{9,}"), # Character spam (10+ repeats)
]
self.caps_threshold = 0.7 # 70% caps in a message triggers
self.caps_min_length = 10 # Only check messages longer than 10 chars
self.max_emotes = 15 # Max emotes per message
self.whitelist_users = set() # User IDs that bypass automod
def check_message(self, user_id, username, message):
"""
Check a message against all auto-moderation rules.
Returns:
dict with "action" (None, "delete", "timeout", "ban") and "reason",
or None if the message is clean.
"""
if user_id in self.whitelist_users:
return None
text = message.strip()
# Check banned words
text_lower = text.lower()
for word in self.banned_words:
if word.lower() in text_lower:
return {
"action": "timeout",
"duration": 10,
"reason": f"Banned word detected: {word}",
"delete": True,
}
# Check banned patterns (links, character spam)
for pattern in self.banned_patterns:
if pattern.search(text):
return {
"action": "delete",
"reason": "Message matched a banned pattern (link or spam)",
"delete": True,
}
# Check excessive caps
if len(text) >= self.caps_min_length:
alpha_chars = [c for c in text if c.isalpha()]
if alpha_chars:
caps_ratio = sum(1 for c in alpha_chars if c.isupper()) / len(alpha_chars)
if caps_ratio >= self.caps_threshold:
return {
"action": "delete",
"reason": f"Excessive caps ({int(caps_ratio * 100)}%)",
"delete": True,
}
return None # Message is clean
Integrate the AutoModerator into your Kick chat bot's message processing loop — every incoming message gets checked before the command system runs. If the auto-mod returns an action, the bot deletes the message and optionally times out the user, all without human intervention.
Kick Chat Bot EventSub: Real-Time Webhooks
Sending messages and moderating chat requires knowing when messages arrive. The Kick API provides EventSub — a webhook-based system that pushes real-time events to your server. When a viewer sends a chat message, follows the channel, subscribes, or gifts subs, Kick sends an HTTP POST to your webhook URL. This is how your Kick chat bot listens to chat without polling.
Setting Up EventSub Webhooks for Your Kick Chat Bot
To receive events, your Kick chat bot must register a webhook subscription via the EventSub API. The webhook URL must be publicly accessible (HTTPS required in production), so for local development you will need a tunneling tool like ngrok or cloudflared:
"""
Kick Chat Bot — EventSub webhook registration and handler.
Subscribes to chat messages, follows, and subscriptions.
"""
def subscribe_to_events(access_token, broadcaster_id, webhook_url, webhook_secret):
"""
Subscribe to EventSub events for a channel.
The webhook_secret is used to verify that incoming webhooks are from Kick.
"""
events = [
"chat.message.sent",
"channel.followed",
"channel.subscription.new",
"channel.subscription.gifted",
]
results = []
for event_type in events:
response = requests.post(
f"{API_BASE}/public/v1/events/subscriptions",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={
"type": event_type,
"version": "1",
"condition": {"broadcaster_user_id": str(broadcaster_id)},
"transport": {
"method": "webhook",
"callback": webhook_url,
"secret": webhook_secret,
},
},
)
if response.status_code in (200, 201, 202):
print(f"[EVENTSUB] Subscribed to {event_type}")
results.append({"event": event_type, "status": "ok"})
else:
print(f"[EVENTSUB ERROR] {event_type}: {response.status_code} {response.text}")
results.append({"event": event_type, "status": "failed"})
return results
Kick Chat Bot Webhook Receiver
Once subscribed, Kick sends events to your webhook URL. Your Kick chat bot needs a web server to receive these, verify the signature, and process the event. Here is a Flask-based webhook receiver that ties together the command system, auto-moderation, and event handling:
"""
Kick Chat Bot — Webhook receiver.
Receives EventSub events and routes them to the appropriate handler.
"""
import hmac
import hashlib
from flask import Flask, request as flask_request, jsonify
webhook_app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_here" # Must match the secret used in subscription
def verify_webhook_signature(payload, signature, secret):
"""Verify that the webhook was sent by Kick using HMAC-SHA256."""
expected = hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@webhook_app.route("/webhook", methods=["POST"])
def handle_webhook():
"""
Main webhook endpoint for the Kick chat bot.
Receives all EventSub events and routes them to handlers.
"""
# Verify signature
signature = flask_request.headers.get("X-Kick-Signature", "")
if not verify_webhook_signature(flask_request.data, signature, WEBHOOK_SECRET):
return jsonify({"error": "Invalid signature"}), 401
event = flask_request.json
event_type = event.get("type", "")
# Handle verification challenge (sent when you first subscribe)
if "challenge" in event:
return jsonify({"challenge": event["challenge"]})
# Route events to handlers
if event_type == "chat.message.sent":
handle_chat_message(event["data"])
elif event_type == "channel.followed":
handle_follow(event["data"])
elif event_type == "channel.subscription.new":
handle_subscription(event["data"])
elif event_type == "channel.subscription.gifted":
handle_gift_sub(event["data"])
return jsonify({"status": "ok"}), 200
def handle_chat_message(data):
"""
Process an incoming chat message through the Kick chat bot pipeline:
1. Auto-moderation check
2. Command parsing
3. Response delivery
"""
username = data.get("sender", {}).get("username", "unknown")
user_id = data.get("sender", {}).get("user_id")
message_text = data.get("content", "")
message_id = data.get("message_id", "")
is_mod = data.get("sender", {}).get("is_moderator", False)
# Step 1: Auto-moderation
automod_result = auto_moderator.check_message(user_id, username, message_text)
if automod_result:
if automod_result.get("delete"):
delete_message(access_token, message_id)
if automod_result["action"] == "timeout":
timeout_user(access_token, broadcaster_id, user_id,
automod_result.get("duration", 10),
automod_result.get("reason", "Auto-mod"))
if automod_result["action"] == "ban":
ban_user(access_token, broadcaster_id, user_id,
automod_result.get("reason", "Auto-mod"))
return
# Step 2: Command processing
response = bot.handle_message(username, message_text, message_id, is_moderator=is_mod)
if response:
reply_to_message(access_token, broadcaster_id, response, message_id)
def handle_follow(data):
"""Welcome new followers in chat."""
username = data.get("follower", {}).get("username", "someone")
send_message(access_token, broadcaster_id,
f"Welcome to the channel, {username}! 💚 Thanks for the follow!")
def handle_subscription(data):
"""Thank new subscribers in chat."""
username = data.get("subscriber", {}).get("username", "someone")
send_message(access_token, broadcaster_id,
f"🎉 {username} just subscribed! Welcome to the family!")
def handle_gift_sub(data):
"""Announce gift subs in chat."""
gifter = data.get("gifter", {}).get("username", "someone")
count = data.get("quantity", 1)
send_message(access_token, broadcaster_id,
f"🎁 {gifter} gifted {count} sub(s)! Absolutely legendary!"
The Complete Kick Chat Bot: Putting It All Together
Here is the main entry point that wires authentication, the command system, auto-moderation, and the webhook server into a single runnable Kick chat bot:
#!/usr/bin/env python3
"""
Kick Chat Bot — Main entry point.
Authenticates, registers commands, subscribes to events, and starts the webhook server.
"""
# ─── Global instances (populated at startup) ─────────────────────
access_token = None
broadcaster_id = None
bot = None
auto_moderator = None
CHANNEL_SLUG = "your-channel-slug"
WEBHOOK_URL = "https://your-server.com/webhook" # Must be HTTPS in production
WEBHOOK_SECRET = "your_random_secret_string_here"
def main():
global access_token, broadcaster_id, bot, auto_moderator
# Step 1: Authenticate
print("=" * 50)
print(" Kick Chat Bot — Starting up")
print("=" * 50)
access_token = authenticate()
if not access_token:
print("[FATAL] Authentication failed. Exiting.")
return
# Step 2: Get channel info
channel = get_channel_info(access_token, CHANNEL_SLUG)
if not channel:
print("[FATAL] Could not find channel. Exiting.")
return
broadcaster_id = channel["broadcaster_user_id"]
# Step 3: Initialize command system
bot = KickChatBot(access_token, broadcaster_id, prefix="!")
# Register custom commands
@bot.command("discord", cooldown=30)
def discord_cmd(username, args):
return "Join our Discord: https://discord.gg/your-invite"
@bot.command("lurk", cooldown=0)
def lurk_cmd(username, args):
return f"{username} is now lurking. Enjoy the stream! 👀"
@bot.command("hug", cooldown=3)
def hug_cmd(username, args):
target = args.strip() if args.strip() else "chat"
return f"{username} hugs {target}! 🤗"
# Step 4: Initialize auto-moderation
auto_moderator = AutoModerator()
# Step 5: Subscribe to EventSub events
print("\n[SETUP] Subscribing to EventSub events...")
subscribe_to_events(access_token, broadcaster_id, WEBHOOK_URL, WEBHOOK_SECRET)
# Step 6: Send startup message
send_message(access_token, broadcaster_id, "🤖 Bot is online! Type !commands to see what I can do.")
# Step 7: Start webhook server
print("\n[READY] Kick chat bot is running. Waiting for events...")
webhook_app.run(host="0.0.0.0", port=8080, debug=False)
if __name__ == "__main__":
main()
Deploying Your Kick Chat Bot to a VPS
Running your Kick chat bot on your local machine means it dies when you close the terminal or restart your PC. For 24/7 uptime, deploy to a VPS (Virtual Private Server). Any Ubuntu 22.04+ VPS with 512MB RAM is sufficient — Kick chat bots are lightweight.
Step 1: Set Up the VPS
# SSH into your VPS
ssh user@your-server-ip
# Install Python and pip
sudo apt update && sudo apt install -y python3 python3-pip python3-venv
# Create project directory
mkdir ~/kick-chat-bot && cd ~/kick-chat-bot
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install requests flask gunicorn
Step 2: Upload Your Kick Chat Bot Files
# From your local machine, upload the bot files
scp -r ./*.py user@your-server-ip:~/kick-chat-bot/
scp kick_tokens.json user@your-server-ip:~/kick-chat-bot/
# Set secure permissions on the token file
ssh user@your-server-ip "chmod 600 ~/kick-chat-bot/kick_tokens.json"
Step 3: Create a systemd Service for Your Kick Chat Bot
A systemd service ensures your Kick chat bot starts automatically on boot, restarts on crashes, and logs output to the system journal:
sudo nano /etc/systemd/system/kick-chat-bot.service
[Unit]
Description=Kick Chat Bot
After=network.target
[Service]
Type=simple
User=user
WorkingDirectory=/home/user/kick-chat-bot
ExecStart=/home/user/kick-chat-bot/venv/bin/python3 main.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable kick-chat-bot
sudo systemctl start kick-chat-bot
# Check status
sudo systemctl status kick-chat-bot
# View live logs
sudo journalctl -u kick-chat-bot -f
Your Kick chat bot is now running 24/7. If it crashes, systemd automatically restarts it after 10 seconds. If the VPS reboots, the bot starts on boot. Use journalctl to monitor logs in real time.
Kick Chat Bot Project Structure
Once you have all the pieces together, your Kick chat bot project folder should look like this:
kick-chat-bot/
├── main.py # Entry point — wires everything together
├── auth.py # OAuth 2.1 PKCE authentication module
├── chat.py # Message sending and channel info functions
├── commands.py # KickChatBot class with command system
├── moderation.py # Ban, timeout, unban, delete functions
├── automod.py # AutoModerator class with spam filters
├── webhook.py # Flask webhook receiver for EventSub
├── kick_tokens.json # Saved OAuth tokens (auto-generated, never commit)
├── requirements.txt # pip dependencies
├── .gitignore # Must include kick_tokens.json
└── .env # CLIENT_ID, CLIENT_SECRET, etc. (never commit)
Common Errors When Building a Kick Chat Bot
When building and deploying your Kick chat bot, these are the issues you will encounter most frequently:
| Error | Cause | Fix |
|---|---|---|
401 Unauthorized |
Access token expired or was revoked | Delete kick_tokens.json and re-authenticate, or check the token refresh logic. |
403 Forbidden |
Token lacks the required scope | Verify your app requests all needed scopes. Delete the old token and re-authorize with the correct scopes. |
403 Forbidden on moderation |
Bot account is not a moderator in the channel | Add your bot account as a moderator in Kick channel settings. |
404 Not Found on channel lookup |
Incorrect channel slug or broadcaster ID | Double-check the slug (case-sensitive). Use GET /public/v1/channels?slug=your-slug to verify. |
429 Too Many Requests |
Rate limit exceeded | Implement exponential backoff. Reduce message frequency. Kick limits vary by endpoint. |
| EventSub webhook not receiving events | Webhook URL not publicly accessible or HTTPS missing | Use ngrok for local dev. In production, ensure your server has a valid SSL certificate and port 443/8080 is open. |
| OAuth redirect URI mismatch | The redirect URI in the request does not match the one registered in the Developer Portal | Ensure exact match including protocol, host, port, and path. Use localhost instead of 127.0.0.1. |
| Bot messages not showing BOT badge | Message type field set to "user" instead of "bot" |
Set "type": "bot" in the chat POST body. |
Kick Chat Bot vs Twitch Bot: Key Differences
If you are migrating from Twitch bot development, here are the main differences to be aware of when building a Kick chat bot:
| Feature | Twitch | Kick |
|---|---|---|
| Chat protocol | IRC (WebSocket) | REST API + EventSub webhooks |
| Authentication | OAuth 2.0 | OAuth 2.1 with mandatory PKCE |
| Bot identification | No built-in badge (uses username) | Native BOT badge via "type": "bot" |
| Real-time events | EventSub (webhooks or WebSocket) | EventSub (webhooks only, as of March 2026) |
| Rate limits | Well-documented per endpoint | Less documented — implement backoff defensively |
| Ecosystem maturity | Hundreds of libraries and frameworks | Early stage — most bots are custom-built |
| Moderation | REST API + IRC commands | REST API only |
The biggest architectural difference is that Twitch bots typically maintain a persistent WebSocket connection to IRC for receiving chat messages, while a Kick chat bot receives messages via HTTP webhooks. This means your Kick chat bot must run a web server (like Flask or FastAPI) to receive events, rather than simply opening a socket connection.
Security Best Practices for Your Kick Chat Bot
- Never commit tokens or secrets: Add
kick_tokens.json,.env, and any file containing your Client Secret to.gitignore. Use environment variables on the server. - Verify webhook signatures: Always validate the
X-Kick-Signatureheader on incoming webhooks using HMAC-SHA256. Without this check, anyone could send fake events to your Kick chat bot. - Use a dedicated bot account: Never run your Kick chat bot on your personal or streamer account. If the bot token is compromised, only the bot account is at risk.
- Request minimal scopes: Only request the scopes your bot actually uses. A Kick chat bot that only sends messages does not need
moderation:ban. - Set file permissions on the VPS: Run
chmod 600 kick_tokens.json .envso only the bot's OS user can read them. - Implement rate limiting in your bot: Even if Kick does not strictly enforce limits on every endpoint, sending hundreds of messages per minute will get flagged. Limit your Kick chat bot to 1–2 messages per second maximum.
Frequently Asked Questions About Building a Kick Chat Bot
Skip the Dev Work: Automate Kick Content Distribution with Repostit.io
Building a Kick chat bot gives you control over chat interactions, but if your goal is distributing stream clips and content across Kick, YouTube Shorts, TikTok, and Instagram Reels — that is a different problem entirely. Maintaining separate API integrations, OAuth flows, and upload pipelines for each platform is a full engineering project on its own.
Repostit.io handles cross-platform content distribution automatically. Upload your stream highlights once, and Repostit.io formats, optimizes, and publishes them across every platform simultaneously — no separate API keys, no token management, no cron jobs. For streamers who want to maximize reach beyond chat automation, it eliminates the entire publishing pipeline. For a complete technical breakdown of the Kick API endpoints used under the hood, see our Kick API Guide.