---
url: https://docs.sunlicense.com/how-to/discord-py-bot.md
description: >-
  How to add license keys to a discord.py bot you sell - check the buyer's key
  with the SunLicense Python SDK in setup_hook without blocking the event loop,
  close on a rejection, keep running offline, and re-check with a tasks.loop.
---

# 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](https://github.com/sunlicense-com/example-discord-bot-js)
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](https://sunlicense.com/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](https://sunlicense.com/start?ref=docs-howto-discord-py).

## 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](/guide/sdks#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](/guide/concepts#validation-result-codes) (mask the key in logs),
   anything else runs and retries in about 10 minutes, or uses an [offline license](/guide/offline-licenses).
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](/integrations/gumroad), [Lemon Squeezy](/integrations/lemon-squeezy), [Payhip](/integrations/payhip),
[Stripe](/integrations/stripe), [Tebex](/integrations/tebex) and the [other stores](/integrations/) issue a key per
purchase and revoke it on a refund. The [SunLicense Discord bot](/guide/discord) can give buyers their key and a role in
your support server.

## Going further

* [Obfuscation](/guide/obfuscation) for the Python you ship, and [protection](/guide/protection) to make part of the bot
  run only with a valid license.
* Selling a command-line tool instead? See [Python CLI](/how-to/python-cli).
* [discord.js](/how-to/discord-js-bot) and [JDA](/how-to/jda-bot) versions of this guide. More on
  [licensing Discord bots](https://sunlicense.com/for/discord-bots/).
