# How to Set Up Discord Integration with ChocolateLMLite Webhooks

> Easily integrate ChocolateLMLite with Discord webhooks. Learn to configure webhook URLs and JSON payloads to send LLM responses directly to your Discord server.

- Repository: [Segment (gpsnmeajp)/chocolatelmlite](https://github.com/gpsnmeajp/chocolatelmlite)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Configure ChocolateLMLite to POST generated LLM responses to a Discord webhook URL by enabling the global `EnableWebhook` flag and setting persona-specific `webhook_url` and `webhook_body` fields to match Discord's expected JSON payload structure.**

ChocolateLMLite, an open-source LLM assistant framework maintained by gpsnmeajp, supports real-time HTTP webhooks that forward AI-generated responses to external services. Setting up Discord integration with ChocolateLMLite webhooks allows you to stream chat completions directly into a Discord channel using Discord's native webhook API and the placeholder substitution system implemented in the source code.

## How ChocolateLMLite Webhooks Work

The webhook system operates asynchronously to prevent blocking the UI during HTTP requests. When the LLM finishes generating a response, the system checks the global configuration and persona-specific settings before dispatching the payload.

According to the implementation in [[`src/LLM.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs#L462-L480), the execution flow follows these steps:

1. Verify `fileManager.generalSettings.EnableWebhook` is **true**.
2. Confirm the active persona has non-empty `WebhookUrl` and `WebhookBody` properties.
3. Replace placeholders (`%text%`, `%id%`, `%name%`) in the `WebhookBody` template with the actual response text, persona ID, and persona name.
4. Wrap the POST operation in `Task.Run` to execute `HttpClient.PostAsync` on a background thread.
5. Log the HTTP status code or exception via `MyLog.LogWrite`.

The placeholder substitution uses simple string replacement before the JSON payload is sent, allowing you to inject dynamic content into static webhook templates.

## Prerequisites

Before configuring the integration, ensure you have the following:

- **ChocolateLMLite** installed and running (version supporting webhooks, commit `main` branch or later).
- **Discord server permissions** to create webhooks (Manage Webhooks permission).
- A target Discord channel where messages should appear.

## Configuration Methods

You can enable Discord integration using three approaches: the web UI, direct JSON editing, or the REST API.

### Method 1: Using the Web UI

The simplest approach uses the settings interface defined in [`static/setting.htm`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/static/setting.htm#L68-L76):

1. Navigate to **System Settings** in the ChocolateLMLite interface.
2. Locate the **Webhook** section (lines 68-76 in the source).
3. Check **Enable Webhook** to set `EnableWebhook` to true in [[`src/FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs#L22-L28).
4. Enter your Discord Webhook URL in the **Webhook URL** field.
5. Set the **Webhook Body** to `{"content":"%name%: %text%"}` to format messages with the persona name prefix.

### Method 2: Editing Persona JSON Directly

For advanced configuration, modify the persona JSON file directly. The parsing logic in [[`src/Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs#L22-L30) reads these fields:

```json
{
  "name": "Assistant",
  "webhook_url": "https://discord.com/api/webhooks/1234567890/AbCdEfGhIjKlMnOpQrStUvWxYz",
  "webhook_body": "{\"content\":\"%name%: %text%\"}",
  "EnableWebhook": true
}

```

Ensure the `webhook_body` contains valid JSON with escaped quotes, as the deserializer expects a string value that will be used as the POST body template.

### Method 3: API Configuration

You can also configure webhooks programmatically via the REST API. According to [[`API.md`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/API.md)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/API.md#L158-L176), send a PATCH request to update persona settings:

```bash
curl -X PATCH http://localhost:5000/api/persona/assistant \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://discord.com/api/webhooks/1234567890/AbCdEfGhIjKlMnOpQrStUvWxYz",
    "webhook_body": "{\"content\":\"%name%: %text%\"}",
    "EnableWebhook": true
  }'

```

The API validates the JSON structure before saving to the persona configuration file.

## Discord-Specific Payload Formatting

Discord webhooks expect specific JSON schemas. ChocolateLMLite's placeholder system allows you to construct valid Discord payloads dynamically.

### Basic Text Messages

For simple text forwarding, use the default template:

```json
{"content":"%text%"}

```

This maps directly to Discord's `content` field, which accepts up to 2000 characters of plain text or Discord markdown.

### Including Persona Identity

To identify which persona generated the response in multi-bot setups, include the name placeholder:

```json
{"content":"**%name%**: %text%"}

```

The `**` syntax renders bold text in Discord, making the persona name stand out in the channel.

### Advanced Embeds (Optional)

While ChocolateLMLite sends the raw `webhook_body` string as-is, you can construct Discord embed objects by manually editing the JSON:

```json
{
  "content": null,
  "embeds": [{
    "title": "Response from %name%",
    "description": "%text%",
    "color": 3447003
  }]
}

```

Ensure the JSON is minified and properly escaped when stored in the persona configuration string.

## Testing Your Webhook Integration

Before deploying to production, verify the integration using a local test server. The repository includes a Flask example in [[`API.md`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/API.md)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/API.md#L39-L65) that captures incoming webhooks:

```python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    data = request.get_json()
    print("[Webhook received]", data)
    return jsonify({"status": "ok"}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

```

Run the server with:

```bash
pip install flask
python webhook_server.py

```

Then configure ChocolateLMLite with `webhook_url` set to `http://localhost:5000/webhook`. Generate a test message in the chat interface; you should see the Discord-formatted JSON payload printed in your terminal, confirming the placeholder substitution and POST execution from [[`src/LLM.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs#L462-L480) is functioning correctly.

## Troubleshooting Common Issues

### Webhook Not Firing

If messages do not appear in Discord, verify the global flag in [[`src/FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs#L22-L28):

- Ensure **Enable Webhook** is checked in System Settings.
- Check that `fileManager.generalSettings.EnableWebhook` evaluates to `true` in the runtime state.

### 400 Bad Request Errors

Discord returns HTTP 400 if the JSON payload is malformed. Common causes:

- **Unescaped quotes**: When editing `webhook_body` manually in JSON files, ensure inner quotes are escaped as `\"`.
- **Missing `content` field**: Discord requires at least one of `content` or `embeds`. Verify your template includes `"content":"%text%"` or similar.

### Asynchronous Delay

Because the webhook executes inside `Task.Run` as shown in [[`src/LLM.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs#L462-L480), failures are logged asynchronously and may not trigger immediate UI errors. Check the application logs (via `MyLog.LogWrite`) for status codes or exception messages if messages fail to arrive.

## Summary

- **ChocolateLMLite** supports Discord integration via HTTP webhooks configured in [[`src/FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs#L22-L28) and [[`src/Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs#L22-L30).
- Enable the global **`EnableWebhook`** flag, then set the **`webhook_url`** (Discord URL) and **`webhook_body`** (JSON template) for each persona.
- Use placeholders **`%text%`**, **`%id%`**, and **`%name%`** to dynamically insert response data into Discord's expected JSON format, typically `{"content":"%name%: %text%"}`.
- The actual POST request executes asynchronously in [[`src/LLM.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs#L462-L480) using `HttpClient.PostAsync`, ensuring the UI remains responsive during transmission to Discord.

## Frequently Asked Questions

### How do I find my Discord webhook URL?

Navigate to your Discord channel settings, select **Integrations** → **Webhooks** → **New Webhook**, then copy the generated URL. This URL follows the format `https://discord.com/api/webhooks/{id}/{token}` and must be pasted into the **Webhook URL** field in ChocolateLMLite's settings (defined in [`static/setting.htm`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/static/setting.htm#L68-L76)) or directly into the persona JSON file parsed by [[`src/Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs#L22-L30).

### Why are my messages not appearing in Discord even though the webhook is enabled?

First, verify that the global **`EnableWebhook`** flag in [[`src/FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs#L22-L28) is set to `true` in the System Settings UI. Next, confirm that the active persona has valid non-empty values for both `WebhookUrl` and `WebhookBody` (checked in [[`src/LLM.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs#L462-L480)). Finally, ensure your `webhook_body` contains valid JSON with properly escaped quotes, as Discord returns HTTP 400 errors for malformed payloads.

### Can I customize the Discord message format beyond simple text?

Yes, the **`webhook_body`** field accepts any valid Discord webhook JSON payload. While the default template `{"content":"%text%"}` works for basic text, you can construct complex **embed objects** by manually editing the JSON string in the persona configuration. For example, use `{"embeds":[{"title":"Response from %name%","description":"%text%","color":3447003}]}` to send rich embeds. Ensure the JSON is minified and inner quotes are escaped as `\"` when stored in the configuration file parsed by [[`src/Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs#L22-L30).

### Does the webhook block the chat interface while sending to Discord?

No, the webhook execution is fully **asynchronous**. According to the implementation in [[`src/LLM.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs)](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/LLM.cs#L462-L480), the HTTP POST is wrapped in `Task.Run`, which executes the `HttpClient.PostAsync` call on a background thread. This design ensures that even if Discord's API experiences latency or temporary outages, the ChocolateLMLite chat interface remains responsive and the LLM generation flow continues uninterrupted. Errors are logged via `MyLog.LogWrite` for debugging without disrupting the user experience.