Docs
Setup, widget, SDK, internal client WebSocket, reverse proxy, styling, and CDN assets.
Why OwnWire?
HTTPS encrypts data in transit, but TLS private keys are often held by reverse proxies, CDNs, and load balancers. When these systems terminate TLS, your message data becomes visible to third-party infrastructure—creating security and compliance risks.
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?");
Golang SDK
The OwnWire Go SDK allows you to integrate end-to-end encrypted communication into any Go application—not just browsers. Use it to build custom clients, backend services, CLI tools, or anywhere you need secure WebSocket messaging with OwnWire's encryption layer.
With the Go SDK, you can connect to OwnWire servers from command-line tools, desktop applications, microservices, or any Go-based system. Your messages remain end-to-end encrypted between any clients using the SDK and your internal websocket handler.
- Custom clients — Build desktop or CLI applications that communicate securely
- Backend-to-backend — Encrypt messages between your own services
- Testing tools — Create automated test clients for your OwnWire deployment
- Integration services — Connect third-party systems with end-to-end encryption
# Get the SDK
go get code.ownwire.net/OwnWire/ownwire-go-sdk
# Read full documentation at:
# https://code.ownwire.net/OwnWire/ownwire-go-sdk
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>