OwnWire

End-to-end Data Encryption Beyond SSL/TLS

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.

Two WebSockets
  • Public WebSocket -- browsers / SDK / embeddable widget
  • Internal WebSocket -- your backend worker / bot / LLM client

Local test / demo

Show options
CLI help
ownwire --help
Start OwnWire
Local run
./ownwire --log-level=info
Run the example internal client (Ruby)
Connects to internal WS and replies
  1. Put your OpenAI API key into a file: .openai_key
  2. Install Ruby + Bundler: bundle install
  3. Start the WS client:
    bundle exec ruby internal_ws_client.rb --openai-api-key-fn=.openai_key
Example client archive: internal_ws_client.tar.gz
Open the demo page
Widget embed example
http://localhost:8080/embed_example.html

Setting up in production

Install binary
Put it on PATH
cp ownwire /usr/local/bin/
chmod +x /usr/local/bin/ownwire
systemd service
Run as a daemon
[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
Enable service
Reload + enable + start
systemctl daemon-reload
systemctl enable ownwire
systemctl start ownwire

Reverse proxy (nginx)

Why you may want this

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.
nginx config
WSS upgrade + proxying
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;
  }
}
Point ws_url to wss://ownwire.yourwebsite.com/ws. Do not manually set Sec-WebSocket-* headers in nginx.
Enable nginx site
Symlink + test + reload
ln -s /etc/nginx/sites-available/ownwire /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Secure origins

The widget and SDK only work on https://* or on http://localhost. Insecure origins are not supported.

Using the JavaScript SDK

Include the SDK
One script tag
<script src="https://ownwire.yourwebsite.com/js/ownwire.js"></script>
Connect + send
Minimal example
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.

Use cases
  • 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
Repository & Documentation
Full SDK docs and examples
# Get the SDK
go get code.ownwire.net/OwnWire/ownwire-go-sdk

# Read full documentation at:
# https://code.ownwire.net/OwnWire/ownwire-go-sdk
Read the full documentation, API reference, and examples at code.ownwire.net/OwnWire/ownwire-go-sdk

Adding the chat widget

Embed
Script + init
<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>
Behavior

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

Concept
Two sockets, routed by session_id
browser  →  OwnWire public WS  →  OwnWire internal WS  →  your client
browser  ←  OwnWire public WS  ←  OwnWire internal WS  ←  your client
Example URLs
Local vs production
ws://127.0.0.1:8081/ws
wss://ownwire.yourwebsite.com/ws
Message format
Text frames containing JSON
{
  "content":    "... message text ...",
  "session_id": "UUID string",
  "metadata":   "optional metadata string"
}
Notes
  • 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.
Pseudocode
Language agnostic
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

Two ways
  • Recommended: append overrides using --custom-css.
  • Advanced: replace /css/style.css at the nginx layer.
Simple
Append overrides
./ownwire --custom-css=/path/to/custom.css
Advanced
Full replacement via nginx
location = /css/style.css {
    alias /var/www/ownwire-custom/style.css;
    add_header Cache-Control "no-cache";
}
Example overrides
Green theme
/* 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; }
Isolation

The widget is isolated in an iframe, so your widget CSS will not affect the rest of your website.

Serving assets from a CDN

Extract assets
Dump embedded JS/CSS
./ownwire --extract-assets=./public
After extracting, OwnWire exits. Upload ownwire_assets/ to your CDN.
Example CDN layout
URLs
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
Start OwnWire with assets root
Rewrites HTML to CDN
./ownwire --assets-url="https://mycdn.com/mycompany/ownwire/"
Before
Local assets
<link href="/css/style.css" rel="stylesheet">
<script src="/js/application.js" type="module"></script>
After
CDN assets
<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>
Widget embed with CDN script
Load widget JS from CDN
<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>
After this, CSS/JS comes from the CDN, while OwnWire handles WebSocket traffic only.