- Escape angle brackets around IRC username so Owncast doesn't swallow them as HTML tags (<nick> instead of <nick>) - Register a chat user via POST /api/chat/register to obtain an accessToken, then pass it as a query param when connecting to /ws (Owncast closes the WebSocket immediately without one) - Cache the access token across reconnections; re-register only on rejection - Add ws_display_name config option (default "IRC Bridge") - Fix echo suppression: record_sent and is_echo now both compare raw body instead of mismatched formatted/raw values Made-with: Cursor
owncast-irc-bridge
Bidirectional chat bridge between Owncast and IRC. Messages sent in your Owncast chat appear in an IRC channel and vice versa.
Quick Start (Docker Compose)
1. Create your config file
cp config.example.toml config.toml
Edit config.toml with your IRC server/channel and Owncast URL.
2. Get an Owncast access token
In your Owncast admin panel, go to Integrations > Access Tokens and create a token with "send messages" permission.
3. Set the token
export OWNCAST_ACCESS_TOKEN="your-token-here"
Or create a .env file (git-ignored):
OWNCAST_ACCESS_TOKEN=your-token-here
4. Configure the Owncast webhook
In your Owncast admin, go to Integrations > Webhooks and add a webhook pointing to:
http://<bridge-host>:9078/webhook
Select the events: Chat Message, Stream Started, Stream Stopped.
5. Run it
docker compose up -d
Check logs:
docker compose logs -f
Running Without Docker
Requires Rust 1.75+.
cargo build --release
export OWNCAST_ACCESS_TOKEN="your-token-here"
./target/release/owncast-irc-bridge --config config.toml
Configuration
See config.example.toml for all options. The only required sections are [irc] (with server and channel) and [owncast] (with url). Everything else has defaults.
| Section | Key | Default | Description |
|---|---|---|---|
irc |
server |
(required) | IRC server hostname |
irc |
port |
6667 |
IRC server port |
irc |
tls |
false |
Use TLS for IRC |
irc |
nick |
owncast-bridge |
IRC nickname |
irc |
channel |
(required) | IRC channel to join |
owncast |
url |
(required) | Owncast instance URL |
owncast |
webhook_port |
9078 |
Port the webhook server listens on |
owncast |
websocket_enabled |
false |
Also connect via WebSocket (redundant with webhook, useful as fallback) |
owncast |
health_poll_interval_secs |
30 |
How often to poll Owncast status |
bridge |
irc_prefix |
[IRC] |
Prefix for IRC messages in Owncast |
bridge |
owncast_prefix |
[OC] |
Prefix for Owncast messages in IRC |
control |
socket_path |
/tmp/owncast-irc-bridge.sock |
Unix socket for bridge-ctl |
The access token is always read from the OWNCAST_ACCESS_TOKEN environment variable (not the config file).
Runtime Control
Use bridge-ctl to interact with a running bridge:
bridge-ctl status # Show bridge status as JSON
bridge-ctl irc reconnect # Reconnect to IRC
bridge-ctl owncast reconnect # Reconnect to Owncast
bridge-ctl quit # Shut down the bridge
Inside Docker:
docker compose exec bridge bridge-ctl status
How It Works
- Owncast → IRC: Owncast sends webhook events to the bridge. The bridge formats the message and sends it to IRC via PRIVMSG.
- IRC → Owncast: The bridge listens for PRIVMSG in the configured channel and posts to Owncast via the integration API.
- Deduplication: If both webhook and WebSocket are enabled, duplicate messages are detected by ID and dropped.
- Echo suppression: Messages the bridge itself sent are recognized and not re-bridged.
- Stream events: Stream start/stop events are announced in IRC.
- Health polling: The bridge polls Owncast's
/api/statusendpoint and announces state changes in IRC.