WebSockets in a mobile app are already installed. What costs you is what the phone does to the connection.
A WebSocket is a long lived two way connection between your app and your server, and in a React Native and Expo project you do not install it. React Native ships the global WebSocket class, so one line in a file you already have opens a connection. That is also where the help stops. React Native documents the whole feature in one sentence and one code sample, and its API reference entry for the class carries a notice saying the page is work in progress and sending you to MDN. Everything hard about a socket on a phone lives in what neither page covers: the connection dies when the app goes to the background, nothing reconnects it for you, and the standard gives JavaScript no heartbeat to notice with. Opening one at launch also lands in your app startup time.
What follows is written for somebody who was handed the project rather than somebody choosing a protocol. What you already have, what the phone takes away, when a socket is the wrong answer, and why one that works on your laptop refuses in a release build. Every figure and quote below was read on 17 October 2026.
Start hereThe short version
You get the connection free. You pay for the reconnection.
React Native describes its WebSocket class in one sentence: the global WebSocket class, as defined in Web specifications. That sentence is the contract. The thing in your project behaves like the thing MDN documents, which means the WHATWG WebSockets standard and RFC 6455, the WebSocket Protocol, are what you are actually holding. Learn it from MDN and you have learned the one in your app.
The gap is everything a browser tab never had to deal with. A tab stays open while somebody reads it. A phone screen locks, the user takes a call, the train enters a tunnel, wifi hands over to mobile data. None of that is in the standard, and none of it is in React Native's pages. It is all in your code, and if nobody wrote it, your app simply stops updating and never says so.
Do you already have WebSockets, or do you have to add something?
You already have them. React Native's networking page, last updated on 14 October 2026, says it also supports WebSockets, a protocol which provides full-duplex communication channels over a single TCP connection. Its sample is four handlers on a bare constructor: open, message, error, close. No import, no plugin, no entry in your app config. Read the whole page and that section is that one sentence plus the code block, which is genuinely all of React Native's WebSocket documentation.
The API reference is thinner still, and honest about it. The entry for the global WebSocket class, last updated 12 August 2026, opens with a warning that the page is work in progress, so please refer to the MDN documentation for more information. The body underneath it is a single sentence. That is not a complaint. It is the fastest way to know where to look, and it is why the rest of this page quotes MDN rather than guessing at React Native internals.
Expo adds nothing here either. Checking Expo's documentation index on 17 October 2026, across roughly 1,680 pages, there is no WebSocket page and no socket module in the SDK list. That absence is the right outcome rather than a hole: the class is already in the runtime, so there is nothing for Expo to ship. Practically, this means a socket leaves no trace in your dependency list. If you grep package.json for a socket library and find nothing, that tells you nothing. Grep the source for the word WebSocket instead.
If you do find a library in package.json, it was added for behaviour, not for the connection. Reconnection with a backoff, rooms, acknowledgements and fallbacks are the parts the built in class does not have. Knowing which one you are looking at matters before you change anything, because a library usually has its own idea about retries, and ripping it out means writing that idea yourself.
React Native, Networking, last updated 14 October 2026, read 17 October 2026
Check yours
What the screen actually needs
Tick what the feature has to do. A socket is one of three answers, and it is the most expensive one to keep running.
Nothing ticked. Most screens in most apps are one fetch when the screen opens. That is not a lack of ambition, it is the cheapest thing that works and the easiest thing to debug at nine on a Sunday.
What breaks when the phone leaves your hand?
Start with what the standard promises, because it is less than people assume. MDN describes the WebSocket API as making it possible to open a two way interactive communication session between the user's browser and a server, so that you can send messages and receive responses without having to poll. The interfaces are WebSocket, CloseEvent and MessageEvent. There is a fourth, WebSocketStream, which MDN marks as non-standard and supported in only one rendering engine, so ignore it in an app.
The connection has four states and the numbers are worth memorising because you will read them in a log: CONNECTING is 0, OPEN is 1, CLOSING is 2, CLOSED is 3. Calling send before the open event throws an InvalidStateError, because the socket is still CONNECTING. Every attempt ends in one of two events, open or close, so there is no third outcome to handle. And send is asynchronous. It does not wait for transmission, it adds the data to an internal buffer and starts sending. On a bad connection that means send keeps returning quietly while the queue grows behind it. The one number that tells you is bufferedAmount, the bytes not yet transmitted, counted on the UTF-8 encoding of any buffered text.
Now the part that is nobody's documentation. The standard exposes no heartbeat to JavaScript. MDN's own guide to writing client applications has no protocol level ping or pong in it; the ping in its example is the literal text ping, sent on a timer by the application. So every keepalive in every app is application code that somebody wrote or forgot to write. A socket that has silently died looks identical to a socket where nobody is talking, and only your own timer can tell the difference.
Backgrounding is the same story with higher stakes. React Native's AppState page, last updated 27 September 2026, defines active, background, and an iOS only inactive state for transitions and interruptions such as an incoming call. It says nothing at all about networking, and neither does the networking page. There is no first-party sentence in React Native's documentation about what happens to an open socket when the app goes to the background, so do not plan from one. Plan from the consequence instead: your JavaScript is not running, so assume the connection is gone, treat reconnection as the normal path, and refetch state on reconnect rather than assuming you missed nothing.
One performance note, because it surprises people who came from a web app. Every frame that arrives is handed to your JavaScript, and a parse of each one runs on the same thread that is deciding what the screen looks like. A chatty socket on a list that is scrolling competes directly with the scroll. That thread, and what the runtime does with the work on it, is the ground that Hermes and the new architecture change, and it is worth knowing which one your project is on before you blame the socket.
MDN, The WebSocket API, last modified 12 September 2026, read 17 October 2026
When is a socket the wrong tool for a phone?
A socket earns its place when the server has to speak first, often, at times you cannot predict, while a person is watching. A chat thread that is open. A score during a match. A driver moving on a map. Two people in the same document. In all four, the useful life of a message is a couple of seconds, and there is no request to hang the answer off.
It is the wrong tool in three situations, and the first one is absolute. If the update has to arrive while the app is closed, no socket can deliver it, because a socket needs your JavaScript running and your JavaScript is not running. That is a push notification, which is a separate system with separate credentials and a separate delivery story. Second, if the data changes a few times an hour, a refetch when the screen comes into focus is less code, fails more loudly and costs nothing to keep alive. Third, if the user is already telling you when to ask, by pulling a list down to refresh, a socket adds a second path into the same state that can disagree with the first.
The cost nobody prices up front is on the server, not the phone. A request is stateless: it arrives, it is served, it is gone, and ten thousand of them a day fit on anything. A socket is a connection your server holds open per user for as long as they have the app open. That changes how you host, how you deploy, how you scale and what a restart does to people mid-sentence. The close code list below has an entry for exactly that, 1012 Service Restart, which exists because it happens. This is the kind of commitment that shows up later in what an app costs to maintain, long after the feature that justified it shipped.
The alternative is already in your project too, which is the part people forget while they are being told that polling is primitive. React Native's networking page documents fetch as the default for networking and notes that XMLHttpRequest is built in as well, so libraries that depend on it work. A refetch on focus is three lines. If a feature can be built either way, build the cheap one first and add the socket when you can point at the moment the cheap one visibly failed a user.
React Native, Networking, fetch and XMLHttpRequest, read 17 October 2026
Why does a socket that works on your laptop fail in a release build?
Usually the scheme. MDN's constructor reference allows ws, wss, http and https, forbids a fragment in the URL, and resolves a relative URL against the base URL of the calling script, which is a sensible rule in a browser and a trap in an app. Write the address absolute and write it as wss. MDN is blunt on the security side: WebSockets should not be used in a mixed content environment, most browsers now only allow secure WebSocket connections, and in a real application the connection should use wss. React Native's own sample uses a ws address, which is shorthand for a documentation page rather than advice for something you ship.
Android presses from its own direction. Android's network security configuration documentation, read on 17 October 2026, says cleartext support is enabled by default up to Android 8.1, API level 27, and disabled by default starting with Android 9, API level 28, with apps that need it opting in. A ws address is cleartext. So if a release Android build cannot open a connection that opens happily from your machine, that default is the first thing to check, before you touch any application code. The second thing to check is duller: a hostname or a localhost address that only resolves on your own network has never been heard of by a phone on mobile data.
Then authentication, which is where native apps differ from the web in a way that catches people. React Native's networking page carries a caution that the security model for XMLHttpRequest is different from the web because there is no concept of CORS in native apps. No browser is refusing anything on your behalf, and the same applies to a socket: your server authenticates the connection or nobody does. The standard constructor takes a url and optional protocols and nothing else, and MDN says the handshake headers are added automatically, so there is no header argument to put a token in. The three real options are a query string, which every proxy and access log in the path will keep; the sub-protocol value, set through the protocols argument, where MDN notes only one sub-protocol is selected per connection; or the first message after open, which means your server holds an unauthenticated connection briefly and has to time it out.
Last, closing properly, because this is small and strictly defined. MDN says close takes an optional code and reason, the code must be either 1000 or a custom value in the range 3000 to 4999, and the reason must be no longer than 123 bytes encoded in UTF-8. Break either and you get an exception rather than a disconnection: an InvalidAccessError for a code outside those ranges, a SyntaxError for a reason that is too long. Your own codes belong in 4000 to 4999, which MDN describes as private use, by prior agreement between the two ends. Agree them with whoever owns the server and write them down somewhere you will find again.
MDN, The WebSocket API, constructor, close and security notes, read 17 October 2026
The number in the close event, and whose fault it is
When a socket closes you get a code and sometimes a reason. Logging them is the single cheapest thing you can do to a real time feature, because the code tells you whether to retry, to deploy a fix or to do nothing. Meanings below are MDN's, read on 17 October 2026. The last two columns are judgement, not standard.
| Code | What MDN says it means | Who did it | Reconnect |
|---|---|---|---|
| 1000 Normal Closure | The connection successfully completed the purpose it was created for | Either end, on purpose | No |
| 1001 Going Away | The endpoint is going away, through a server failure or a page navigating away | The other end | Yes |
| 1006 Abnormal Closure | Reserved. The connection closed with no close frame sent, when a status code was expected | Nobody said anything. The network went | Yes |
| 1011 Internal Error | The server hit an unexpected condition and could not fulfil the request | Your server | Yes |
| 1012 Service Restart | The server is terminating the connection because it is restarting | Your deploy, most likely | Yes |
| 1013 Try Again Later | A temporary condition, for example the server is overloaded and casting off some clients | Your server, deliberately | Yes, after a wait |
| 3000 to 3999 | For libraries, frameworks and applications, registered directly with IANA | Something in your package.json | Read its docs |
| 4000 to 4999 | Private use, by prior agreement between the two ends. Cannot be registered | You and whoever wrote your server | Your rule to write |
Where this lands if a builder generated your app
Nothing on this page changes because an agent typed the project instead of a person. Newly is an AI app builder: you describe an app in plain English and it writes a real React Native and Expo project that you own, with two way GitHub sync so the code is somewhere you can read it. The WebSocket global is in that project for the same reason it is in every React Native project. It came with the framework. Nobody added it and you cannot remove it.
Owning the code is the point and also the bill. The reconnection, the backoff, the offline banner and the heartbeat are yours to keep, which is the practical end of the argument about who owns AI generated code. Newly is paid from $25 a month with no free tier, it uploads iOS builds to TestFlight, it publishes to Google Play internal testing and it can build a standalone Android APK. It does not submit to the App Store for you, because Apple requires the content owner to submit.
There is one timing detail worth planning around. A TestFlight build and a Google Play internal testing build are the first time the app runs without your development server, which makes them the first place a ws address or a laptop hostname will fail. Check the socket on the first internal build rather than the first release, and ask for the reconnect behaviour by name when you describe the feature, because a request for live updates is not a request for a retry policy. The honest limit is simple: no generator can decide what your app should do when the train goes into a tunnel.
Questions people ask about WebSockets in a mobile app
No. React Native ships the global WebSocket class, so new WebSocket('wss://example.com/socket') works in a file you already have, with no import and no entry in package.json. React Native's networking documentation puts it plainly: it also supports WebSockets, a protocol which provides full-duplex communication channels over a single TCP connection. Expo adds nothing on top. Reading Expo's documentation index on 17 October 2026 there is no WebSocket page and no socket module in the SDK list, because the framework already provides the class. If you do find a socket library in package.json, somebody chose to add it for the reconnection and fallback behaviour, not for the connection itself.
Describe the screen, then say what happens when it drops
Write down the feature, then write down the sentence after it: what the user sees while the connection is gone, and what the app does when it comes back. That second sentence is the whole of the work, and it is the one people leave out.
Start building