# Linking, devices and profiles (/en/docs/wa/sessions)

Read this page when you connect `wa` to your WhatsApp account for the first time, when you add a second account, or
when `wa` says a profile is not linked. At the end you know how to link, how to check who a profile is, how to
unlink, and where `wa` keeps what it needs to stay linked.

> ⚠️ **`wa` is an unofficial client of your real WhatsApp account.** WhatsApp may ban an account that uses one, and
> bans accounts that send in bulk or write to strangers. `wa` writes only when a command asks, limits sends and holds
> every write when WhatsApp says to slow down. Do not send in bulk or to strangers ([limits](/llms.mdx/docs/wa/limits/content.md)).

First, the words this page uses:

* **Linked device**: `wa` on this computer, as WhatsApp sees it. You link it from the phone, the way you link
  WhatsApp Web, and the phone lists it under Settings → Linked devices. Its keys live in one file on this computer.
  Whoever has that file can read and send as you, so treat it like your password.
* **History push**: the copy of your recent chats the phone sends once, seconds after linking. `wa` keeps what
  arrives; older messages of one chat can be asked of the phone later
  ([older history](/llms.mdx/docs/wa/archive/content.md#older-history-from-your-phone)).
* **Profile**: a name for one linked device on this computer, with its own keys and settings. Every command that
  uses an account names it, so you pick one name at setup (`wa personal setup`) and use it from then on
  ([profiles](#profiles)).

## First run [#first-run]

`wa personal setup` guides a first run in five steps: this computer's folders, the link, the account, five chats
from the local store, and the agent skill (`--agent codex`, `cursor`, `claude`, `gemini`, `all` or `none`). Allow
about five minutes, most of it the history push. Run it in a terminal. A profile linked before is checked without
linking again, and nothing is asked. Before the QR code or the pairing code, setup and `session start`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-session-start "Command reference: wa session start") print the
warning above on stderr, once per link. `wa personal session start` is the command for the link alone.

## Linking [#linking]

```sh
wa personal session start          # a QR code in the terminal
wa personal session start phone    # your number, typed in the terminal, then a code to type on the phone
```

**QR:** scan the code in WhatsApp on the phone: Settings → Linked devices → Link a device. The code is renewed while
you wait.

**Phone:** type your number in international format when `wa` asks. `wa` shows an eight-character pairing code;
on the phone, open Settings → Linked devices → Link a device → Link with phone number instead, and type it. The
number goes to the engine on its input, never as an argument, so other programs on this computer cannot see it in
the list of processes.

Right after either, the phone pushes your history. `session start`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-session-start "Command reference: wa session start") keeps the same connection open, saves what
arrives into the local store, and stops when the phone has been quiet for 45 seconds, or at Ctrl+C. Ctrl+C there,
or `--timeout`, ends only this wait: the device stays linked, what arrived is kept and counted, and the command ends
with exit code `0`. `wa personal setup` waits the same way, then goes on to its remaining steps under the
command's usual time limit, started afresh. Then keep receiving with `wa personal server install`
([the local store](/llms.mdx/docs/wa/archive/content.md)).

### When an agent runs the link [#when-an-agent-runs-the-link]

Your AI agent can start the link for you. Without a terminal, `wa` prints the QR code as plain text, and the agent
pastes it into the chat for you to scan:

```sh
wa personal setup --agent claude --json
wa personal session start qr
```

The agent runs the command in the background, since it waits for your scan. WhatsApp sends a new code after 60
seconds and then every 20 seconds; the agent pastes each new one and says the old one expired. With `--json`, each
code is also one line on stderr: `{"event":"qr","qr":{"text":…,"expires_at":null,"scan":…}}`. The text is about 73
columns wide. Linking is not offered over MCP: an app that only has MCP asks you to run the command. A pairing
code is for you to run in your own terminal: an agent never asks you for your phone number or a code in the chat.

## Check and unlink [#check-and-unlink]

```sh
wa personal account show     # who this profile is linked as; the phone number shows only its last four digits
wa account list              # every profile on this computer, and the account each is linked as
wa personal session end      # unlink the device on WhatsApp's side, and delete its keys here
```

## Change your name, about and photo [#change-your-name-about-and-photo]

```sh
wa personal account update --first-name Ana --last-name Silva   # WhatsApp keeps one name: "Ana Silva"
wa personal account update --description "Back on Monday"       # the "about" line
wa personal account update --photo ~/me.jpg                     # the profile photo, a square JPEG
```

The name goes to the phone and your other devices at once. Other people probably see it only once the phone next
comes online: a device tells WhatsApp its name when it says it is online, and `wa` never does. A
device linked a few minutes ago may not have WhatsApp's settings keys yet; keep `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") running a while and try again.

For a profile photo, `wa` requires a square JPEG from 190 to 720 pixels per side, using the same validation as for a group photo. `wa` does not convert or crop images and rejects PNG or WebP content before changing anything. When name, about and photo are supplied together, they are changed in that order; a later error names the changes already made. Remove the photo from the phone.

`wa personal session end` removes the device **on WhatsApp's side too**: it disappears from the phone's list of linked
devices. It leaves the local store as it is. While `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") runs for the profile, it refuses (exit code `12`): stop
`serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") first with `wa personal server stop`, or Ctrl+C where it runs.

## One connection per device [#one-connection-per-device]

WhatsApp allows one connection for each linked device; a second one makes WhatsApp drop the first. So one `wa`
command at a time may connect a profile. `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve"), `watch`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-watch "Command reference: wa watch") and every command that connects take the device's lock
first; another command for the same profile is refused and names the holder:

```text
this WhatsApp login is in use by serve (pid 4242)
```

`serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") is the exception. While it runs, a command for the same profile that would connect does not take the lock:
it runs inside `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve"), on `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve")'s connection. It reaches `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") through a socket next to `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve")'s lock
(`<state>/serve/<profile>.sock`, mode `0600`, so only your own user can open it). This covers sends, edits,
deletions, reactions, marking read, downloads, group and people commands, `watch`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-watch "Command reference: wa watch"), `doctor --online`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-doctor "Command reference: wa doctor") and the MCP
server `wa <profile> mcp`. The send guard and the profile's permissions still apply. A command run this way never
asks: a write at permission level `ask` needs `--yes` (`--allow-dangerous` for a deletion), or it is refused and
says so.

* **Not through `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve"):** linking and unlinking (`session start`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-session-start "Command reference: wa session start"), `session end`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-session-end "Command reference: wa session end"), `setup`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-setup "Command reference: wa setup")) and `mcp --http`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-mcp "Command reference: wa mcp").
  `chats list`[↗](/llms.mdx/docs/wa/commands-personal/content.md#wa-chats-list "Command reference: wa chats list") and `chats show`[↗](/llms.mdx/docs/wa/commands-personal/content.md#wa-chats-show "Command reference: wa chats show") read the local store, so they never connect. `--offline` and `--dry-run` runs do
  not connect at all.
* **`serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") is starting or does not answer:** the command fails. It never opens a second connection.
* **`wa` was upgraded and `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") not restarted:** the command runs inside the older `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve"), with its code. When the
  two speak different relay versions, the command is refused and says to restart `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve"). `wa upgrade`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-upgrade "Command reference: wa upgrade") restarts
  running servers itself; otherwise use `wa <profile> server restart`.
* **Windows:** commands do not go through `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve"). While `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") runs, a command that connects is refused as above,
  with exit code 12.

With no `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") running, each command connects on its own. While one of them holds the device, or `wa personal mcp`,
which keeps its connection for two minutes after a call, `serve`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-serve "Command reference: wa serve") cannot start. Reads never connect, so they work
beside any of them.

## How long a link lives [#how-long-a-link-lives]

Until it is ended: by `wa personal session end`, from the phone's list of linked devices, or by WhatsApp itself. The
phone must come online now and then, or WhatsApp logs its linked devices out. When the device was removed, every
command that connects says so with exit code `4`; link again with `wa personal session start`
([WhatsApp logged this device out](/llms.mdx/docs/wa/troubleshooting/content.md#whatsapp-logged-this-device-out)).

## Profiles [#profiles]

A profile is a separate linked device: its own keys, settings, recipients and runs. It is named by **the first
word**, not by an option:

```sh
wa personal chats list     # profile "personal"
wa work chats list         # profile "work"
export WA_PROFILE=work     # the same for the whole shell session
```

**The first word is the profile when it is not a command.** A profile therefore cannot be called `chats`[↗](/llms.mdx/docs/wa/commands-personal/content.md#wa-chats "Command reference: wa chats"),
`messages`[↗](/llms.mdx/docs/wa/commands-personal/content.md#wa-messages "Command reference: wa messages") or any other command word. Names are letters, digits, dot, dash and underscore, starting with a letter or
a digit.

The order, first match wins: the first word, then `WA_PROFILE`. With neither, a command that uses an account stops
with exit code `2` and lists the profiles on this computer. `wa` never picks one, not even the only one. Commands
that use no account run without a profile: `--help`, `--version`, `commands`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-commands "Command reference: wa commands"), `skill show`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-skill-show "Command reference: wa skill show"), `account list`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-account-list "Command reference: wa account list") and
`config show`[↗](/llms.mdx/docs/wa/commands-setup/content.md#wa-config-show "Command reference: wa config show").

**`WA_PROFILE_LOCK` pins a process to one profile.** Set it where an agent runs, and a first word or `WA_PROFILE`
naming any other profile is refused (exit code `5`). Without it, an agent could pick a profile with fewer limits.

What each profile is for: [profiles](/llms.mdx/docs/wa/profiles/content.md).

## Where the parts are kept [#where-the-parts-are-kept]

| What | Where |
| --- | --- |
| the device keys | `~/.local/share/whatsapp-cli/profiles/<profile>/private.db`, readable by your user only |
| who holds the one connection | `private.db.lock` beside it |
| chats and messages | the WireCat store every WireCat CLI shares, `~/.local/share/WireCat/wirecat.db` |
| settings | `~/.config/whatsapp-cli/config.json`; never a secret |

`WA_CONFIG_DIR`, `WA_STATE_DIR` and `WA_CACHE_DIR` move the three folders ([configuration
reference](/llms.mdx/docs/wa/configuration-reference/content.md)).

## Next [#next]

* [Read your first chats](/llms.mdx/docs/wa/usage/content.md)
* [Settings, and the order they resolve in](/llms.mdx/docs/wa/configuration/content.md)
* [What reaches the disk, and what never does](/llms.mdx/docs/wa/security/content.md)
