New: image reposting, and Repostit for Claude and ChatGPT. See what’s new
RepostitLog inStart free trial
New: image reposting and AI plugins. See what’s new
RepostitTry it free

How to Build a Kick Chat Bot with Python (2026 Kick API Guide)

· 26 min read

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

  1. 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).
  2. Click Create Application.
  3. Fill in the application name (e.g., my-kick-chatbot), description, and set the redirect URI to http://localhost:3000/callback for local development.
  4. 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-Signature header 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 .env so 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

Is it free to build a Kick chat bot with the Kick API?

Yes. The Kick API is free to use. There are no fees for app registration, API calls, or EventSub subscriptions. You only need to pay for hosting if you deploy your Kick chat bot to a VPS, which typically costs $4–10/month for a basic server.

Does my Kick chat bot need to be a moderator in the channel?

Only if you want to use moderation features like banning, timing out users, or deleting messages. For sending chat messages and responding to commands, the bot only needs the chat:write scope and does not need moderator status.

Can I run a Kick chat bot on multiple channels simultaneously?

Yes. A single Kick chat bot application can subscribe to EventSub events on multiple channels and send messages to any channel where it has been authorized. You need to register separate EventSub subscriptions for each channel's broadcaster_user_id.

How do I get real-time chat messages for my Kick chat bot?

Use the Kick EventSub system. Subscribe to the chat.message.sent event with a webhook URL. Kick sends an HTTP POST to your server every time a message is sent in the subscribed channel. Your bot processes the message and responds via the chat POST endpoint.

What is the difference between type "user" and type "bot" in chat messages?

When your Kick chat bot sends a message with type set to "bot", Kick displays a BOT badge next to the username in chat. When type is set to "user", the message appears as a normal chat message without the badge. Use "bot" for automated responses and "user" for messages that should look like they came from a regular account.

Can I build a Kick chat bot without a VPS?

You can develop and test locally using ngrok or cloudflared to tunnel webhooks to your machine. However, for 24/7 operation during streams, you need a server that stays online. A VPS with 512MB RAM running Ubuntu is sufficient and costs around $4–5/month from providers like DigitalOcean or Hetzner.

How is a Kick chat bot different from a Twitch bot?

Twitch bots connect to chat via IRC over WebSocket and maintain a persistent connection. Kick chat bots receive messages via HTTP webhooks (EventSub) and send responses via REST API calls. Kick also uses OAuth 2.1 with mandatory PKCE instead of OAuth 2.0, and provides a native BOT badge for bot messages.

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.

Post once. Repostit shares it everywhere else.

14 days free. No credit card needed.

Start your free trial

Keep reading

Best Repost Apps for TikTok, Instagram Reels and YouTube Shorts (2026)
SoftwareBest Repost Apps for TikTok, Instagram Reels and YouTube Shorts (2026)Which repost app to use for sharing others’ posts, reposting your own videos to other apps, and bringing back old content, plus apps to avoid.Oct 22, 2026
How to Repost Top-Performing Content Automatically (Without Spamming Your Audience)
SoftwareHow to Repost Top-Performing Content Automatically (Without Spamming Your Audience)Find your proven evergreen posts, refresh them, automate cross-platform distribution, and measure each repost without flooding your audience.Aug 22, 2026
How to Automate YouTube Shorts Python  (2026 API Guide)
How ToHow to Automate YouTube Shorts Python (2026 API Guide)How to Automate YouTube Shorts Python Uploads (2026 API Guide) If you are managing a faceless channel, a brand account, or generating AI content at volume, manual publishing is a massive bottleneck. To scale your reach, learning how to automate YouTube Shorts Python uploading is essential. By programmatically interacting with the YouTube Data API v3, […]Mar 5, 2026