Docs
Setup, widget, SDK, internal client WebSocket, reverse proxy, styling, and CDN assets.
Problem
HTTPS is common, but TLS private keys are often held by reverse proxies (CDNs, load balancers, etc.). Those systems terminate TLS, so traffic is visible to them.
OwnWire adds an additional public-key session encryption layer on top of WebSockets, so only your frontend and your internal WS client can decrypt messages. The reverse proxy in the middle only sees ciphertext.
- Public WebSocket -- browsers / SDK / embeddable widget
- Internal WebSocket -- your backend worker / bot / LLM client
Local test / demo
ownwire --help
./ownwire --log-level=info
- Put your OpenAI API key into a file:
.openai_key - Install Ruby + Bundler:
bundle install - Start the WS client:
bundle exec ruby internal_ws_client.rb --openai-api-key-fn=.openai_key
http://localhost:8080/embed_example.html
Setting up in production
cp ownwire /usr/local/bin/
chmod +x /usr/local/bin/ownwire
[Unit]
Description=OwnWire secure messaging service
After=network.target
[Service]
ExecStart=/usr/local/bin/ownwire --log-level=warn
Restart=always
User=www-data
Group=www-data
WorkingDirectory=/var/lib/ownwire
Environment=OWNWIRE_PORT=8080
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable ownwire
systemctl start ownwire
Reverse proxy (nginx)
Use a reverse proxy when you want TLS termination, a stable public origin, and a single entry point for both your site and OwnWire. nginx can handle certs and routing, while OwnWire still keeps message contents end-to-end encrypted above TLS.
- TLS + public origin -- browsers connect to HTTPS/WSS.
- One front door -- route /ws and assets cleanly.
- Same model -- proxy forwards ciphertext.
server {
listen 443 ssl;
server_name ownwire.yourwebsite.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
proxy_read_timeout 75s;
proxy_send_timeout 75s;
location = /ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header Origin $http_origin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $http_host;
proxy_set_header Origin $http_origin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
ln -s /etc/nginx/sites-available/ownwire /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
Secure origins
Using the JavaScript SDK
<script src="https://ownwire.yourwebsite.com/js/ownwire.js"></script>
let ownwire = new Ownwire("wss://ownwire.yourwebsite.com/ws");
ownwire.onMessage = (msg) => console.log("received:", msg);
ownwire.connect();
ownwire.send("What's the weather in Brazil?");
Adding the chat widget
<script src="https://ownwire.yourwebsite.com/js/ownwire_widget.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
ownwireWidget({
ws_url: "wss://ownwire.yourwebsite.com/ws",
metadata: "username:user1",
title: "Chat with our agent",
widget_origin: "https://ownwire.yourwebsite.com",
widget_path: "/",
minimized_mode: "bar", // "bar" (default) or "icon"
icon_url: "https://ownwire.yourwebsite.com/assets/chat-icon.png" // optional
});
});
</script>
The widget appears in the corner and communicates securely through OwnWire. You can choose a minimized bar (default) or an icon launcher. If icon_url is omitted, OwnWire uses its default icon.
Working with the internal websocket
browser → OwnWire public WS → OwnWire internal WS → your client
browser ← OwnWire public WS ← OwnWire internal WS ← your client
ws://127.0.0.1:8081/ws
wss://ownwire.yourwebsite.com/ws
{
"content": "... message text ...",
"session_id": "UUID string",
"metadata": "optional metadata string"
}
- Your client connects once and keeps the connection open.
- All sessions arrive on the same internal WS. Always route using session_id.
- OwnWire may send pings; most WS libraries handle pongs automatically.
function handle_incoming(raw_json):
data = parse_json(raw_json)
content = data["content"]
session_id = data["session_id"]
metadata = data.get("metadata")
if content is empty:
log("empty message, ignoring")
return
reply_text = process_message(content, metadata)
reply = {
"content": reply_text,
"session_id": session_id,
"metadata": metadata
}
ws.send(encode_json(reply))
main():
ws_url = "ws://127.0.0.1:8081/ws" # or wss://... in production
ws = connect_websocket(ws_url)
ws.on_open = function():
log("internal websocket connected")
ws.on_message = function(event):
try:
handle_incoming(event.data)
except error as e:
log("error processing message: " + e)
ws.on_close = function(code, reason):
log("internal websocket closed: " + code + " / " + reason)
ws.on_error = function(err):
log("websocket error: " + err)
Styling the chat widget
- Recommended: append overrides using --custom-css.
- Advanced: replace /css/style.css at the nginx layer.
./ownwire --custom-css=/path/to/custom.css
location = /css/style.css {
alias /var/www/ownwire-custom/style.css;
add_header Cache-Control "no-cache";
}
/* custom.css: green theme overrides */
.chat { background-color: #2f3b30 !important; }
.chat header { background: #3a4a3a !important; }
.chat .message:not(.reply) { background: #57d38c !important; color: #0d2214 !important; }
.chat .message.reply { background: #465646 !important; }
.chat .send-message { background: linear-gradient(#4fdc87,#3cbf72) !important; }
.chat .send-message:hover { background: linear-gradient(#5aed95,#43d67f) !important; }
.chat .typing-dot { background: #c4f5d6 !important; }
The widget is isolated in an iframe, so your widget CSS will not affect the rest of your website.
Serving assets from a CDN
./ownwire --extract-assets=./public
https://mycdn.com/mycompany/ownwire/css/style.css
https://mycdn.com/mycompany/ownwire/js/application.js
https://mycdn.com/mycompany/ownwire/js/ownwire.js
https://mycdn.com/mycompany/ownwire/js/ownwire_widget.js
./ownwire --assets-url="https://mycdn.com/mycompany/ownwire/"
<link href="/css/style.css" rel="stylesheet">
<script src="/js/application.js" type="module"></script>
<link href="https://mycdn.com/mycompany/ownwire/css/style.css" rel="stylesheet">
<script src="https://mycdn.com/mycompany/ownwire/js/application.js" type="module"></script>
<script src="https://mycdn.com/mycompany/ownwire/js/ownwire_widget.js"></script>
<script>
ownwireWidget({
ws_url: "https://ownwire.yourwebsite.com/ws",
metadata: "username:user1",
title: "Chat with our agent",
widget_origin: "",
widget_path: "/"
});
</script>