WhatsWrapped

What actually happens to a chat export the moment you drop it in

A technical walkthrough of the data path: which thread your WhatsApp export lands on, what crosses back to the main thread, and the exact three values the one server endpoint will accept.

The question behind the question

"Is this safe" almost never means "have you written a privacy policy." It means: does my chat leave this device, and if something does leave, what exactly is in it. Those are answerable in terms of code paths rather than promises, so that is how this piece is written.

Two things make a chat export an unusually sensitive file. It is complete, containing every message in the selected thread rather than a sample, and it is other people's data as much as yours, since the person on the other side never agreed to anything. A tool that handles it responsibly should be able to describe the journey of the file in concrete terms: which process reads it, what leaves that process, and what an endpoint is even permitted to receive.

Step one: the file goes to a Web Worker, not to a server

When you drop a .txt or .zip export in, the file is handed to a Web Worker. A Web Worker is a separate JavaScript thread inside your own browser tab. It exists so heavy work does not freeze the interface, and the side effect that matters here is isolation: the worker gets the file, and the main thread does not.

Inside that worker, the whole job happens. The transcript is split into lines, each line is resolved into a timestamp, a sender, and a body, and every statistic is computed from that structure. Message counts, reply gaps, active days, emoji tallies, repeated phrases, all of it. The worker is where the raw text lives and where it stays.

There is no upload endpoint for the export. That is the flat version of the claim, and it is the one that actually settles the question, because a file cannot be quietly retained by a server that never receives it. Parsing and statistics run on your machine, using your CPU. On a very large export you can feel this, which is itself a tell: the work is local.

Step two: what crosses back to the main thread

A worker communicates with the page by passing messages across a boundary. Everything that crosses that boundary is something we chose to send. In this app, what crosses back is derived numbers and short derived strings: counts, averages, ranked lists, the participant labels the export already contained, the top phrases that survived aggregation. The message bodies do not make the trip.

This is not a technicality. The main thread is what renders the screen, and it is the layer that in a less careful build would be tempted to ship something off to analytics or an error reporter. If the transcript never arrives there, there is nothing at that layer to leak. The boundary between worker and page is doing real work as a privacy control, not just a performance one.

It also explains a limitation people sometimes notice. You cannot ask the recap to show you a specific message, because by the time the interface exists, the interface has only ever seen the arithmetic.

Step three: the one endpoint that exists, and the three values it takes

A tool with genuinely no server calls would be an unusual thing to build and an easy thing to overstate, so here is the honest version. There is one endpoint, /api/wrap/commit, and it exists for metering, which is to say counting that a recap was generated.

Its schema accepts exactly three scalars and rejects anything else. Not "strips extra fields," not "ignores them": the request fails validation. That matters, because this endpoint is the single place a client could try to push chat content through, and the narrowness of the contract is what removes the possibility rather than merely discouraging it.

What lands in the wraps table is correspondingly thin: a date, a message count, whether the chat was a two-person thread or a group, and a language. No participant names. No message text. Not even the statistics you are looking at on screen. A row in that table tells us a recap happened and roughly how big the input was. It cannot tell us who you talk to or what about.

The saved recap is a different table, on purpose

If you save a recap to a library so you can reopen it later, something more does get stored: the computed statistics themselves, including participant names and top phrases. That is unavoidable, because a saved recap that stores nothing is not a saved recap.

What is stored is the output, never the transcript. The messages that produced a top phrase do not travel with it, and there is no path from a saved recap back to the conversation it summarised.

Keeping this in a deliberately separate table from the metering table is a design decision worth naming. Two tables with different contents and different reasons to exist cannot drift into each other. The metering row stays a metering row even for people who never save anything, and the saved recap stays scoped to the one action that created it.

How to verify any of this yourself

Do not take a walkthrough on faith, including this one. Open your browser's developer tools, go to the network tab, and load an export. You will see the page assets load and then, notably, you will not see a request carrying a multi-megabyte body. Generate a recap and watch what the commit call contains. Three values.

The same test works on any chat-analysis tool you are evaluating, and it is a better filter than reading a policy page. A policy describes intent. The network tab describes behaviour. If a tool sends your transcript somewhere, it will show up there regardless of what the marketing copy says.

A second check: pull your network connection after the page has loaded and try to generate a recap. Local parsing keeps working offline up to the point where it needs to record that a recap happened. Server-side parsing simply stops.

What this does not protect you from

Local processing solves the transmission problem. It does not solve every problem, and pretending otherwise would be the same overselling this article is arguing against.

The export file still sits in your downloads folder after you request it from WhatsApp, in plain text, readable by anything with access to that device. Deleting it when you are done is a reasonable habit. Anything you screenshot and send to a group chat has left the local sandbox entirely, by your own hand, which is fine as long as it is deliberate. And the other participant in the conversation still did not consent to being analysed, which is a question about you rather than about the software.

The narrow claim is the accurate one. The chat does not leave your device through this tool. What you do with the recap afterwards is a separate decision, and it is yours.

Try it with your chat

Frequently asked questions

Does WhatsWrapped upload my WhatsApp messages?+

No. There is no upload endpoint for the export. The .txt or .zip file goes into a Web Worker inside your own browser, which parses it and computes every statistic on your device.

If nothing is uploaded, why is there a server endpoint at all?+

One endpoint, /api/wrap/commit, records that a recap was generated. Its schema accepts exactly three scalars and rejects anything else, so chat content cannot be pushed through it.

What exactly is stored in the database?+

The metering table holds a date, a message count, whether the chat was a dyad or a group, and a language. No names, no message text, no statistics. A saved recap is a separate table and stores the computed statistics, never the transcript.

Can I verify the claim myself?+

Yes. Open the network tab in your browser's developer tools before loading an export. A tool that sends your transcript somewhere produces a request with a large body, and you will not see one here.

Why can't the recap show me the actual messages behind a statistic?+

Because the message bodies never cross from the Web Worker back to the page. Only derived numbers and short derived strings make that trip, so the interface has never seen the transcript.

Related terms