Own domain and first-party proxy
How to serve widget files, chat connection, offline form and Smart Answer through a subdomain of your website – with a proxy key, the real visitor address and ready-made templates for nginx, Apache and Caddy.
With your own domain, the chat runs through a subdomain of your website, for example chat.your-company.com. Your server accepts the widget's requests and passes them on to reactive.chat as a reverse proxy. This page is for the person who runs that server. The short version for editors is under Own domain (first-party).
What runs through the subdomain
- Widget files –
loader.js, the core loaded after it (core.<hash>.js) and the other modules. - Chat connection – the WebSocket under
/rc_wss/, including the HTTP fallback under/rc_wss/h/and file uploads under/rc_wss/upload/. - Offline form –
POST /api/offline/<SITE_KEY>, when no chat connection can be established. - Smart Answer –
/api/suche/, if you use the AI answer in your website search.
What you gain:
- No requests to third-party domains. For these routes, your visitors' browsers talk only to your subdomain.
- One address in your Content Security Policy.
script-srcandconnect-srclist your subdomain instead of the reactive.chat addresses (details under Security and CSP). - The real visitor address arrives. Your proxy reports it in a dedicated header, secured by a secret key. Throttling and blocking then count per visitor rather than per proxy.
What it is not:
- Not self-hosting. Conversations, attachments and settings are still processed and stored by reactive.chat.
- No statement about consent. Your own domain does not change who processes the data.
- Your proxy sees the traffic. TLS ends at your proxy; do not log request bodies there, and keep
/rc_wss/h/out of the access log (these requests carry a short-lived connection token in the query).
Requirements
- A server or reverse proxy that is publicly reachable via HTTPS on port 443 and can make outgoing HTTPS connections. There are templates for nginx (with the ssl module), Apache 2.4.47 or later and Caddy 2.
- For Apache, the modules
mod_ssl,mod_proxy,mod_proxy_http,mod_proxy_wstunnel,mod_headersandmod_setenvif. - A subdomain used only for the chat. The chat needs the whole host; a path such as
www.your-company.com/chat/does not work. Enter the name only – withouthttps://, port or path. Not allowed: IP addresses, internal endings such as.local,.lan,.internal, and names underreactive.chat. - The same domain as the website. The chat is first-party only if the subdomain belongs to the domain of the website it is embedded on.
- DNS (A and/or AAAA) with public addresses only.
- A TLS certificate for the subdomain (the nginx/Apache templates expect the certbot paths; Caddy obtains it itself).
- Permission to edit the project.
Setup
- In your account, open the project, section Embed → Set up your own domain.
- Under 1. Host, enter the name, for example
chat.your-company.com, and save. - Under 2. Proxy key, click Create key. The key starts with
rcpx_and is shown exactly once; only a hash is stored. It belongs solely in the proxy configuration – never in a web page, a browser script or a repository. - Under 3. Server configuration, take the template for your server. Right after creating the key, it contains the key; later it shows
__RC_PROXY_KEY__. Test the configuration (nginx -torapachectl configtest) and reload the server. - Under 5. Check, click Check now (at most every 10 seconds). No message is created.
- Only when all five check steps show “OK” do you replace the embed snippet with the one from 4. Embed snippet:
<script src="https://chat.your-company.com/loader.js"
data-rc-site-key="SITE_KEY"
data-rc-ws-url="wss://chat.your-company.com/rc_wss/"
async></script>
The widget takes the location of its files from src, the chat connection from data-rc-ws-url, and derives from it the addresses of the offline form, uploads and HTTP fallback. All other attributes stay as they are (Script tag reference).
What the proxy must forward
/rc_wss/→ chat serverhttps://ws.reactive.chat/rc_wss/, with WebSocket upgrade, a long timeout (templates: 3600 s), unbuffered and with a body limit of at least 32 MB (uploads)./api/offline/→https://ws.reactive.chat/api/offline/(small body, template: 32 kB, 30 s)./api/suche/→https://ws.reactive.chat/api/suche/, unbuffered (the response is a Server-Sent Events stream), body up to 8 kB, 120 s.- everything else → delivery network
https://cdn.reactive.chat/.
For all routes:
- Host header and SNI are the target's name, not your subdomain. An unknown host is answered with HTTP 421.
- The target's certificate is verified.
- No cookies, no credentials. The browser's
CookieandAuthorizationare not passed on, andSet-Cookieis not returned. - No redirects. A cache is not required; if you set one up, keep it short for
loader.js– the file has no version number in its name (the core does, see Architecture).
For /rc_wss/, /api/offline/ and /api/suche/, the proxy sets two headers on every request:
- X-RC-Proxy-Key – your key
rcpx_…. - X-RC-Client-IP – the visitor's address as your proxy sees it.
“Sets” means: whatever a browser sends under these names is overwritten. On the route to the delivery network, both are cleared.
Behind a load balancer or CDN, your proxy first sees that device's address. In that case, set up taking over the real address: nginx real_ip (set_real_ip_from, real_ip_header), Apache mod_remoteip (RemoteIPHeader, RemoteIPTrustedProxy), Caddy trusted_proxies and {client_ip} instead of {remote_host}. The check does not detect this case – it only sees that a valid address is set.
nginx template
The account page already fills in host, target addresses and key – it is best to copy the template from there. The version here is for reference; the placeholders are chat.your-company.com, SITE_KEY and __RC_PROXY_KEY__. On RHEL/CentOS the CA bundle is at /etc/pki/tls/certs/ca-bundle.crt.
server {
listen 80;
listen [::]:80;
server_name chat.your-company.com;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name chat.your-company.com;
ssl_certificate /etc/letsencrypt/live/chat.your-company.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.your-company.com/privkey.pem;
# HTTP fallback (SSE / long polling): token in the query, do not log
location /rc_wss/h/ {
access_log off;
proxy_pass https://ws.reactive.chat/rc_wss/h/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_cache off;
}
# Chat connection (WebSocket) and uploads
location /rc_wss/ {
proxy_pass https://ws.reactive.chat/rc_wss/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
proxy_cache off;
client_max_body_size 32m;
proxy_request_buffering off;
}
# Offline form
location /api/offline/ {
proxy_pass https://ws.reactive.chat/api/offline/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
client_max_body_size 32k;
proxy_read_timeout 30s;
}
# Smart Answer (website search), answer as a stream
location /api/suche/ {
proxy_pass https://ws.reactive.chat/api/suche/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host ws.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name ws.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "__RC_PROXY_KEY__";
proxy_set_header X-RC-Client-IP $remote_addr;
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
client_max_body_size 8k;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 120s;
}
# Widget files
location / {
proxy_pass https://cdn.reactive.chat/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host cdn.reactive.chat;
proxy_ssl_server_name on;
proxy_ssl_name cdn.reactive.chat;
proxy_ssl_verify on;
proxy_ssl_verify_depth 3;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_set_header X-RC-Proxy-Key "";
proxy_set_header X-RC-Client-IP "";
proxy_set_header Cookie "";
proxy_set_header Authorization "";
proxy_hide_header Set-Cookie;
}
}
Apache template (2.4.47 or later)
Only the VirtualHost for port 443; set up the redirect from port 80 and the certificate as for your other hosts. SSLProxyVerifyDepth 5 is required – otherwise Apache checks the target's chain only one level deep and reports “Certificate Chain too long”.
<VirtualHost *:443>
ServerName chat.your-company.com
SetEnvIf Request_URI "^/rc_wss/h/" rc_no_log
CustomLog ${APACHE_LOG_DIR}/chat.your-company.com_access.log combined env=!rc_no_log
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/chat.your-company.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/chat.your-company.com/privkey.pem
SSLProxyEngine on
SSLProxyVerify require
SSLProxyVerifyDepth 5
SSLProxyCheckPeerName on
SSLProxyCACertificateFile /etc/ssl/certs/ca-certificates.crt
ProxyRequests Off
ProxyPreserveHost Off
ProxyAddHeaders Off
RequestHeader unset X-RC-Proxy-Key early
RequestHeader unset X-RC-Client-IP early
RequestHeader unset Cookie early
RequestHeader unset Authorization early
ProxyPass "/rc_wss/" "https://ws.reactive.chat/rc_wss/" upgrade=websocket timeout=3600 flushpackets=on
ProxyPass "/api/offline/" "https://ws.reactive.chat/api/offline/" timeout=30
ProxyPass "/api/suche/" "https://ws.reactive.chat/api/suche/" timeout=120 flushpackets=on
ProxyPass "/" "https://cdn.reactive.chat/"
<Location "/rc_wss/">
RequestHeader set X-RC-Proxy-Key "__RC_PROXY_KEY__"
RequestHeader set X-RC-Client-IP "expr=%{REMOTE_ADDR}"
</Location>
<Location "/api/offline/">
RequestHeader set X-RC-Proxy-Key "__RC_PROXY_KEY__"
RequestHeader set X-RC-Client-IP "expr=%{REMOTE_ADDR}"
Header unset Set-Cookie
</Location>
<Location "/api/suche/">
RequestHeader set X-RC-Proxy-Key "__RC_PROXY_KEY__"
RequestHeader set X-RC-Client-IP "expr=%{REMOTE_ADDR}"
Header unset Set-Cookie
</Location>
</VirtualHost>
Apache before 2.4.47 does not know upgrade=websocket. In that case, replace the first ProxyPass line with:
RewriteEngine on
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/rc_wss/(.*) "wss://ws.reactive.chat/rc_wss/$1" [P,L]
ProxyPass "/rc_wss/" "https://ws.reactive.chat/rc_wss/" timeout=3600 flushpackets=on
Caddy 2 template
header_up with a value replaces whatever the browser sent under this name. If you switch on access logs, exclude /rc_wss/h/* (log_skip, before Caddy 2.8 skip_log).
chat.your-company.com {
handle /rc_wss/* {
reverse_proxy https://ws.reactive.chat {
header_up Host ws.reactive.chat
header_up X-RC-Proxy-Key "__RC_PROXY_KEY__"
header_up X-RC-Client-IP {remote_host}
header_up -Cookie
header_up -Authorization
transport http {
tls_server_name ws.reactive.chat
}
flush_interval -1
}
}
handle /api/offline/* {
reverse_proxy https://ws.reactive.chat {
header_up Host ws.reactive.chat
header_up X-RC-Proxy-Key "__RC_PROXY_KEY__"
header_up X-RC-Client-IP {remote_host}
header_up -Cookie
header_up -Authorization
header_down -Set-Cookie
transport http {
tls_server_name ws.reactive.chat
}
}
}
handle /api/suche/* {
reverse_proxy https://ws.reactive.chat {
header_up Host ws.reactive.chat
header_up X-RC-Proxy-Key "__RC_PROXY_KEY__"
header_up X-RC-Client-IP {remote_host}
header_up -Cookie
header_up -Authorization
header_down -Set-Cookie
transport http {
tls_server_name ws.reactive.chat
}
flush_interval -1
}
}
handle {
reverse_proxy https://cdn.reactive.chat {
header_up Host cdn.reactive.chat
header_up -X-RC-Proxy-Key
header_up -X-RC-Client-IP
header_up -Cookie
header_up -Authorization
header_down -Set-Cookie
transport http {
tls_server_name cdn.reactive.chat
}
}
}
}
The five check steps
Each step shows “OK”, “Error”, “Note” or “not checked”. The check has passed only with five times “OK”. Checks use HTTPS on port 443 only, without redirects, 5 seconds per step.
- Name resolution – A/AAAA present, all addresses public. All following steps connect to exactly this address.
- loader.js via your domain – the file is loaded and compared with ours. Without a connection, an answer or a valid certificate, the remaining steps are skipped. A redirect is an error; a differing file points to a cache or a wrong target for
/. - Key and visitor address – call of
/rc_wss/eigene-domain-probe. Possible errors:X-RC-Proxy-Keyis missing; key unknown (right after creating it, a delay of up to 30 seconds);X-RC-Client-IPis missing or invalid; the key belongs to another project. A note if the key is valid but registered for a different host. - WebSocket –
wss://chat.your-company.com/rc_wss/must answer with HTTP 101 and the subprotocolrc.v1. - Offline form –
GET /api/offline/SITE_KEYmust answer with 405 (the endpoint only accepts POST; that way no message is created).
You can call the check address from step 3 yourself:
curl -s https://chat.your-company.com/rc_wss/eigene-domain-probe
When set up correctly:
{"rc":"eigene-domain-probe","v":1,"schluessel":"gueltig","client_ip":"gesetzt","site":"SITE_KEY","host":"chat.your-company.com"}
- schluessel –
gueltig(valid),unbekannt(unknown) orfehlt(missing). - client_ip –
gesetzt(set),fehlt(missing) orungueltig(invalid). - site, host – only with a valid key. The response never returns an address.
The proxy key
- Rotating: Create new key issues a new one immediately; the previous one stays valid for 24 hours. There are at most two valid keys – rotating again within the 24 hours invalidates the oldest one immediately.
- Removing: Remove key invalidates it immediately (including an old one from a running rotation). Remove own domain removes host and key together – switch the snippet on your website back first.
- Missing or wrong key: the chat keeps running, but the two headers are ignored. All visitors then appear with your proxy's address – and because the abuse brake counts per address, it can throttle your whole proxy and therefore all visitors at once.
- One project per key: if a widget with the site key of another project connects through a proxy with a valid key, the connection is rejected. For a further project you need a separate subdomain with its own key.
- Key leaked: anyone who knows it can claim arbitrary visitor addresses for your project. Remove it, create a new one, enter it.
Troubleshooting
- HTTP 421 (Misdirected Request): the proxy sends your name as the host instead of the target name. nginx:
proxy_set_header Hostplusproxy_ssl_server_name on/proxy_ssl_name; Apache:ProxyPreserveHost Off; Caddy:header_up Hostandtls_server_name. - HTTP 502 / TLS error towards the target: Apache without
SSLProxyVerifyDepth 5; nginx with a wrong path inproxy_ssl_trusted_certificateor withoutproxy_ssl_server_name on; outgoing HTTPS blocked. - HTTP 413 when uploading: without a setting, nginx only accepts 1 MB;
client_max_body_size 32min the/rc_wss/section (and at least the same limit in an upstream load balancer). - No WebSocket upgrade:
Upgrade/Connectionare not passed on. The chat then runs over the HTTP fallback, but only connects after two failed WebSocket attempts. - 401 for the offline form: password protection sits in front of
/api/offline/. - 403 with a note about the origin: the website's domain (not the chat subdomain!) is missing under Allowed origins in the project.
- 403 “The answer does not come from reactive.chat”: a firewall or access rule in front of the target refuses the request.
- DNS points to a private address (
10.0.0.0/8,192.168.0.0/16,100.64.0.0/10,fc00::/7…): the public record must be public.
Viimati uuendatud: