Why WebSocket security is different
HTTP requests are stateless, individually authenticated, and governed by well-understood browser security policies. WebSocket connections are long-lived, stateful, and authenticated only at the handshake. Once the connection upgrades from HTTP to WS, the browser's Same-Origin Policy no longer applies in the same way, CORS headers are irrelevant, and there is no built-in mechanism to re-authenticate on every message.
For fintech applications, this creates unique risks. A real-time payment dashboard might stream transaction amounts, customer IDs, and balance updates. A compromised or hijacked WebSocket connection can eavesdrop on all that data, or worse, inject messages that manipulate the UI or trigger server-side actions.
WSS: TLS is mandatory, not optional
Just as HTTP must be HTTPS, WebSocket must be WSS. Plain ws:// connections transmit every message — including authentication tokens — in cleartext. Any network intermediary (a compromised router, a malicious Wi-Fi hotspot, or a MITM proxy) can read and modify the traffic.
Enforce WSS at the infrastructure level. Configure your load balancer or reverse proxy (NGINX, AWS ALB, Cloudflare) to terminate TLS and reject non-encrypted WebSocket upgrade requests. Do not rely on the application code to enforce this — infrastructure controls cannot be bypassed by a developer accidentally configuring a ws:// endpoint.
Origin validation: your first line of defence
Unlike HTTP requests, WebSocket handshakes are not protected by CORS. The browser sends the Origin header during the upgrade request, but the server must explicitly validate it. If your WebSocket server accepts connections from any origin, an attacker can create a malicious webpage that opens a WebSocket connection to your server, riding on the victim's authenticated session.
Maintain a strict allowlist of permitted origins. During the upgrade handshake, check the Origin header against this list. Reject connections from unknown origins with a 403 response before the upgrade completes. This is the WebSocket equivalent of CORS — but you must implement it manually.
Tokens in WebSocket URLs
We frequently find fintech applications passing JWT tokens as query parameters in the WebSocket URL: wss://api.example.com/ws?token=eyJ.... These URLs appear in browser history, server access logs, proxy logs, and referrer headers. An attacker who accesses any of these sources obtains a valid authentication token. Send authentication tokens in the first message after the WebSocket connection is established, or use a short-lived ticket pattern exchanged via a secure HTTP endpoint.
Authentication: secure the handshake
WebSocket connections must be authenticated before any data flows. The cleanest pattern for fintech applications is the ticket-based approach.
The ticket pattern
The client first makes a standard authenticated HTTP request to a /ws/ticket endpoint, which returns a short-lived, single-use ticket (a random token valid for 30 seconds). The client then opens the WebSocket connection and sends this ticket as the first message. The server validates the ticket, associates the connection with the authenticated user, and invalidates the ticket to prevent reuse.
This approach keeps long-lived tokens out of WebSocket URLs, leverages your existing HTTP authentication stack (including CSRF protection), and ensures that the WebSocket connection is tied to a verified user session. If the ticket expires or is reused, the connection is immediately terminated.
Message validation: trust nothing from the wire
Every message received over a WebSocket connection must be validated with the same rigour as an HTTP request body. Validate message structure (reject malformed JSON), enforce schema validation (expected fields, types, and value ranges), and sanitise any data that will be rendered in a UI or stored in a database.
For payment systems, enforce strict type checking on message fields. An amount field should be a number within expected bounds, a transaction reference should match an expected format, and action types should be from a defined enum. Reject anything outside these bounds and log the violation for investigation.
Rate limiting: prevent message floods
HTTP rate limiting (requests per second per IP/user) does not apply to individual WebSocket messages. An attacker who establishes a single WebSocket connection can send thousands of messages per second, potentially overwhelming your server, triggering expensive database queries, or abusing business logic.
Implement per-connection message rate limiting. Track the message count per connection using a sliding window or token bucket algorithm. If a client exceeds the threshold (e.g., 50 messages per second for a real-time trading interface), send a warning message, then disconnect. Also limit the maximum message size to prevent memory exhaustion attacks — a single 100MB WebSocket message should never reach your application logic.
Connection hijacking prevention
Cross-Site WebSocket Hijacking (CSWSH) occurs when an attacker's page initiates a WebSocket connection to your server using the victim's cookies. Since WebSocket upgrade requests send cookies automatically (unlike fetch with credentials: 'include', which respects CORS), the attacker's page can establish an authenticated connection.
Defend against CSWSH with origin validation (already covered), anti-CSRF tokens in the handshake, and the ticket-based authentication pattern. The ticket approach is the strongest defence because it requires the attacker to obtain a valid ticket from your HTTP endpoint — which is protected by your standard CORS and CSRF controls.
Heartbeats and connection lifecycle
WebSocket connections can silently die. A network change, a proxy timeout, or a mobile device entering sleep mode can leave a connection in a half-open state where the server believes the client is still connected. For payment systems, stale connections waste resources and can mask real-time monitoring gaps.
Implement application-level heartbeats (ping/pong frames) at regular intervals (every 30 seconds). If three consecutive pongs are missed, terminate the connection and clean up associated resources. On the security side, ensure heartbeat frames are authenticated — do not let an unauthenticated connection keep itself alive indefinitely by responding to pings.
Building real-time features for your payment platform? Let us audit your WebSocket implementation.
Get a WebSocket Security AssessmentWebSocket security checklist
- Enforce WSS (TLS) at the infrastructure level — reject plain
ws://connections. - Validate the
Originheader against a strict allowlist during handshake. - Authenticate using the ticket pattern — never pass tokens in WebSocket URLs.
- Validate and schema-check every incoming message.
- Rate limit messages per connection with a token bucket algorithm.
- Set a maximum message size to prevent memory exhaustion.
- Implement application-level heartbeats with authenticated pong responses.
- Terminate idle or unauthenticated connections aggressively.
- Log connection lifecycle events and anomalous message patterns.
WebSocket security is a subset of your broader API security posture. If your HTTP APIs are well-hardened but your WebSocket endpoints are wide open, attackers will pivot to the weaker channel. Review both together.
Related reading
Blog: Secure fintech APIs in Nigeria · Rate limiting for payment APIs · Securing microservices behind an API gateway
Guides: OWASP for fintech · Web app pentest guide · Fintech security checklist
Services: API security · Penetration testing · Authentication security