Skip to content

How to add license keys to a discord.py bot ​

Short answer: pip install sunlicense, read the buyer's key from the environment, check it in setup_hook (in a thread, so the event loop isn't blocked), and close the bot when SunLicense says no. A network error is not a no: keep running and check again later.

python
import asyncio, os, urllib.error
import discord
from sunlicense import SunLicense

licensing = SunLicense(base_url="https://api.sunlicense.com", token=API_KEY)  # API key from your build, never git

class MyBot(discord.Client):
    async def setup_hook(self):
        try:
            r = await asyncio.to_thread(licensing.validate, os.environ["LICENSE_KEY"], host=machine_id())
            if r.status == 400:  # EXPIRED, REVOKED, TOO_MANY_MACHINES...
                print("License rejected:", (r.data or {}).get("code"))
                await self.close()
        except (urllib.error.URLError, TimeoutError) as e:
            print("SunLicense unreachable, running anyway:", e)

MyBot(intents=discord.Intents.default()).run(os.environ["DISCORD_TOKEN"])

validate returns (valid, status, data): a 400 is a rejection with its code in data; network failures raise.

There's no Python bot template yet: the discord.js template shows the same behaviour in full (re-checks, an offline file, masked keys, a /license command) and ports line for line.

Or let your AI agent do it: paste the setup prompt into Claude Code, Cursor or Codex in the bot's folder. It adds the check and proves it with a test license.

Free plan: 3 products, 100 licenses, no credit card. Create a free account.

Step by step ​

  1. In the dashboard: a product, a policy (1 machine = one running copy per license) and an API key, which can only validate licenses.

  2. Install: pip install sunlicense (no dependencies beyond the standard library; it works with nextcord, disnake, py-cord and hikari too).

  3. Keep the API key out of git. The buyer's .env holds DISCORD_TOKEN and LICENSE_KEY; your API key goes into the build you hand out (a generated _build.py your packaging script writes), never the repository.

  4. Machine id: hash something stable and send it as host:

    python
    import hashlib, platform, socket
    def machine_id():
        raw = f"{platform.system()}|{platform.machine()}|{socket.gethostname()}"
        return hashlib.sha256(raw.encode()).hexdigest()

    In Docker set a fixed hostname:, or the id changes with every container. Choosing a machine id.

  5. Don't block the event loop. The SDK is synchronous (urllib); asyncio.to_thread keeps the gateway heartbeat going while it waits.

  6. Results: 200 runs, 400 closes with the reason (mask the key in logs), anything else runs and retries in about 10 minutes, or uses an offline license.

  7. Re-check every few hours with discord.ext.tasks (@tasks.loop(hours=6)), closing on a 400, so a revoked key reaches bots that are already running.

Selling it ​

Gumroad, Lemon Squeezy, Payhip, Stripe, Tebex and the other stores issue a key per purchase and revoke it on a refund. The SunLicense Discord bot can give buyers their key and a role in your support server.

Going further ​

Send feedback

Found something broken, or have an idea? It goes straight to our board.

The first line becomes the title.
Images (optional, up to 5: paste, drop or capture)
Check each image before sending, and remove any that show something private.

We also receive this page's address and your browser version.

Ctrl Enter to send