Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 3 additions & 39 deletions authentication/qr-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: "Authenticate your Baileys socket by scanning a QR code in the What
QR code authentication is the default way to link Baileys to your WhatsApp account. Baileys emits a QR string on the `connection.update` event, which you render however you like — to the terminal, to an image, or to your frontend. Open WhatsApp on your phone, navigate to **Linked Devices**, and scan the code to complete the link.

<Warning>
The `printQRInTerminal` socket option is **deprecated**. Listen for the `qr` field on the `connection.update` event and render the code yourself.
The `printQRInTerminal` socket option is **deprecated** and will be removed in a future version. Listen for the `qr` field on the `connection.update` event and render the code yourself.
</Warning>

## Basic setup
Expand Down Expand Up @@ -49,45 +49,9 @@ QR code authentication is the default way to link Baileys to your WhatsApp accou
</Step>
</Steps>

## Customizing the browser identity
## Customizing the browser identity and receiving full history

When you connect with a QR code, WhatsApp sees your client as a browser. You can control how it identifies itself using the `Browsers` constant exported from Baileys. This affects the name shown in WhatsApp's **Linked Devices** list.

```typescript
import makeWASocket, { Browsers } from '@whiskeysockets/baileys'

// Show as "Ubuntu — My App"
const sock = makeWASocket({
browser: Browsers.ubuntu('My App'),
})
```

A few ready-made presets are available:

| Preset | Example |
| --- | --- |
| `Browsers.ubuntu('My App')` | Ubuntu — My App |
| `Browsers.macOS('Desktop')` | macOS — Desktop |
| `Browsers.windows('My App')` | Windows — My App |

## Receiving full message history

By default, Baileys connects with a Chrome browser profile, which limits how much message history WhatsApp delivers on the initial sync. To request the full history, set `syncFullHistory: true` and use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client eligible for extended history.

```typescript
import makeWASocket, { Browsers } from '@whiskeysockets/baileys'

const sock = makeWASocket({
browser: Browsers.macOS('Desktop'),
syncFullHistory: true,
})
```

History messages arrive asynchronously via the `messaging-history.set` event after the connection opens.

<Note>
Requesting full history can significantly increase startup time and memory usage on accounts with large chat histories.
</Note>
The browser identity Baileys presents to WhatsApp affects how your client appears in **Linked Devices** and how much message history is delivered on first sync. Both options are configured via the socket — see [Configure the Baileys socket connection](/concepts/socket-config) for the `Browsers` presets and the `syncFullHistory` flag.

## Keeping the connection alive

Expand Down
151 changes: 137 additions & 14 deletions concepts/jids.mdx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
---
title: "WhatsApp JIDs explained"
description: "JIDs (Jabber IDs) are how WhatsApp identifies users, groups, and broadcasts in Baileys. Learn the formats and helper functions for working with JIDs."
description: "JIDs (Jabber IDs) are how WhatsApp identifies users, groups, and broadcasts in Baileys. Learn the formats, the PN/LID duality, and helper functions for working with JIDs."
---

WhatsApp identifies every participant — users, groups, broadcast lists, and status feeds — with a **JID** (Jabber ID). JIDs originated in the XMPP protocol and follow the format `local@server`. You will encounter them constantly in Baileys: as the target of `sock.sendMessage`, in the `key.remoteJid` of every incoming message, and as parameters to group and contact queries.

Modern WhatsApp identifies the same person in two different ways depending on context. Both are JIDs:

- **PNJID — Phone Number Jabber Identifier.** Lives on `@s.whatsapp.net` and is derived from the user's phone number. This is the legacy identifier and the one you use when looking someone up by their number.
- **LIDJID — Linked Identity Jabber Identifier.** Lives on `@lid` and is an opaque per-user identifier WhatsApp assigns to anonymize phone numbers in groups, communities, and other shared surfaces. This is the identifier WhatsApp uses by default in Baileys 7.x and later.

Both forms refer to the same underlying account. WhatsApp lets you resolve a PNJID to its LIDJID (but not the reverse) — see [PNJID ↔ LIDJID resolution](#pnjid--lidjid-resolution) below.

## JID formats

<CardGroup cols={2}>
<Card title="User" icon="user">
<Card title="User (PNJID)" icon="user">
`[countrycode][number]@s.whatsapp.net`

Example: `19999999999@s.whatsapp.net`
</Card>
<Card title="User (LIDJID)" icon="user-shield">
`[lid]@lid`

Example: `123456789012345@lid`
</Card>
<Card title="Group" icon="users">
`[timestamp]-[random]@g.us`

Expand All @@ -26,13 +38,28 @@ WhatsApp identifies every participant — users, groups, broadcast lists, and st
<Card title="Stories / Status" icon="circle-play">
`status@broadcast`

This is a fixed constant — all status updates go to this JID.
Fixed constant — all status updates go to this JID.
</Card>
<Card title="Newsletter" icon="newspaper">
`[id]@newsletter`

Example: `12345@newsletter`
</Card>
</CardGroup>

Less common server domains you may see:

| Server | Meaning |
| --- | --- |
| `@hosted` | Hosted PN — phone-number user routed through Meta hosting |
| `@hosted.lid` | Hosted LID — LID-form user routed through Meta hosting |
| `@bot` | Meta AI / first-party bot account |
| `@c.us` | Legacy WhatsApp server domain (still used for `0@c.us`, the official business JID, and PSA messages) |
| `@call` | Voice/video call signaling |

### Phone number rules

When constructing a user JID from a phone number:
When constructing a PNJID from a phone number:

- Include the country code (e.g., `1` for the US, `44` for the UK).
- Do **not** include `+`, `-`, spaces, or parentheses.
Expand All @@ -55,9 +82,59 @@ if (result?.exists) {

---

## PN ↔ LID: the dual-identity model

Since 2024, WhatsApp has been migrating from phone-number identifiers to LIDs (Linked Identity JIDs). A LID is an opaque, per-user identifier that hides the underlying phone number — it lets WhatsApp expose your account inside large groups, communities, and channels without leaking your number to other participants.

In Baileys 7.x and later:

- New Signal sessions are created in LID form by default.
- A single user has both a PNJID (`...@s.whatsapp.net`) and a LIDJID (`...@lid`). They refer to the same person.
- Group participant fields are typically LIDs; `participantAlt` carries the matching PN, and vice versa.
- `MessageKey.remoteJidAlt` and `MessageKey.participantAlt` give you the alternate identifier for direct messages and group/broadcast/channel messages respectively.
- The `Contact` type now exposes a single `id` plus paired `phoneNumber` (when `id` is a LID) and `lid` (when `id` is a PN).

<Info>
Don't try to "restore" PN JIDs in your application. Migrate your storage, indexing, and routing logic to LIDs — WhatsApp treats LIDs as the canonical identifier going forward.
</Info>

### PNJID ↔ LIDJID resolution

WhatsApp lets you resolve a phone number to its LID. The reverse — going from a LID back to a phone number — is not generally supported. Use `onWhatsApp` for one-off PN existence checks, and the `lidMapping` store on `sock.signalRepository` for direct conversions:

```typescript
// PN → LID (single)
const lid = await sock.signalRepository.lidMapping.getLIDForPN(
'19999999999@s.whatsapp.net'
)

// PN → LID (batch — preferred for bulk lookups)
const mappings = await sock.signalRepository.lidMapping.getLIDsForPNs([
'19999999999@s.whatsapp.net',
'447911123456@s.whatsapp.net',
])
// [{ pn: '19999999999@s.whatsapp.net', lid: '...@lid' }, ...]

// LID → PN (only if Baileys has previously seen the mapping)
const pn = await sock.signalRepository.lidMapping.getPNForLID('123456789012345@lid')
```

A `lid-mapping.update` event fires whenever Baileys learns a new PN ↔ LID pair from the wire. For more advanced directory queries (device lists, bulk metadata), see [USync protocol](/advanced/usync).

### Phone number sharing between accounts

Because LIDs hide phone numbers by default, WhatsApp provides explicit opt-in flags to exchange them when both sides agree:

- Businesses can request the recipient's number with `{ requestPhoneNumber: true }` on a sent message.
- Users can share their number with `{ sharePhoneNumber: true }`.

Businesses have used LIDs since 2023; users were rolled in over the course of 2024.

---

## Multi-device JIDs

In the WhatsApp multi-device protocol, a single phone number can have multiple connected devices. Each device gets a device suffix appended: `19999999999:2@s.whatsapp.net`. The part before the `:` is the user, and the number after is the device ID.
In the WhatsApp multi-device protocol, a single account can have multiple connected devices. Each device gets a device suffix appended to the user portion: `19999999999:2@s.whatsapp.net` or `123456789012345:3@lid`. The part before the `:` is the user, and the number after is the device ID.

This is why you must never compare or split JIDs with string operations — a message from device `:0` and a message from device `:2` belong to the same user.

Expand All @@ -74,7 +151,11 @@ import { jidDecode, jidEncode, jidNormalizedUser } from '@whiskeysockets/baileys

// Decode a JID into its components
const decoded = jidDecode('19999999999:2@s.whatsapp.net')
// { user: '19999999999', server: 's.whatsapp.net', device: 2 }
// { user: '19999999999', server: 's.whatsapp.net', device: 2, domainType: 0 }

// Decode a LIDJID
const lidDecoded = jidDecode('123456789012345:3@lid')
// { user: '123456789012345', server: 'lid', device: 3, domainType: 1 }

// Normalize a JID — strips device/agent suffix, lowercases
const normalized = jidNormalizedUser('19999999999:2@s.whatsapp.net')
Expand All @@ -85,6 +166,15 @@ const encoded = jidEncode('19999999999', 's.whatsapp.net')
// '19999999999@s.whatsapp.net'
```

The `domainType` field on the decoded result corresponds to the `WAJIDDomains` enum:

| Value | Constant | Server domain |
| --- | --- | --- |
| `0` | `WHATSAPP` | `s.whatsapp.net` |
| `1` | `LID` | `lid` |
| `128` | `HOSTED` | `hosted` |
| `129` | `HOSTED_LID` | `hosted.lid` |

### Type checks

```typescript
Expand All @@ -95,15 +185,21 @@ import {
isJidNewsletter,
isPnUser,
isLidUser,
isHostedPnUser,
isHostedLidUser,
isJidMetaAI,
areJidsSameUser,
} from '@whiskeysockets/baileys'

isJidGroup('123456789-123345@g.us') // true
isJidBroadcast('1234567890@broadcast') // true
isJidStatusBroadcast('status@broadcast') // true
isJidNewsletter('12345@newsletter') // true
isPnUser('19999999999@s.whatsapp.net') // true
isLidUser('abc123@lid') // true
isPnUser('19999999999@s.whatsapp.net') // true (PNJID)
isLidUser('123456789012345@lid') // true (LIDJID)
isHostedPnUser('19999999999@hosted') // true
isHostedLidUser('123456789012345@hosted.lid') // true
isJidMetaAI('13135550002@bot') // true

// Compare the user portion of two JIDs, ignoring device suffix
areJidsSameUser(
Expand All @@ -112,13 +208,28 @@ areJidsSameUser(
) // true
```

<Warning>
`areJidsSameUser` compares the user portion only — it does not cross PN/LID boundaries. To check whether a PNJID and a LIDJID refer to the same account, resolve both to the same form first via `getLIDForPN`.
</Warning>

<Note>
`isJidUser` from earlier Baileys versions has been removed. Use `isPnUser` or `isLidUser` depending on which form you mean. Both PNs and LIDs are JIDs, so the old name was misleading.
</Note>

### Common constants

```typescript
import { STORIES_JID, S_WHATSAPP_NET } from '@whiskeysockets/baileys'
import {
STORIES_JID,
S_WHATSAPP_NET,
OFFICIAL_BIZ_JID,
META_AI_JID,
} from '@whiskeysockets/baileys'

STORIES_JID // 'status@broadcast'
S_WHATSAPP_NET // '@s.whatsapp.net'
STORIES_JID // 'status@broadcast'
S_WHATSAPP_NET // '@s.whatsapp.net'
OFFICIAL_BIZ_JID // '16505361212@c.us'
META_AI_JID // '13135550002@c.us'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Clarify that META_AI_JID (@c.us) does not match isJidMetaAI (@bot) to avoid a misleading detection example.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At concepts/jids.mdx, line 232:

<comment>Clarify that `META_AI_JID` (`@c.us`) does not match `isJidMetaAI` (`@bot`) to avoid a misleading detection example.</comment>

<file context>
@@ -112,13 +208,28 @@ areJidsSameUser(
+STORIES_JID      // 'status@broadcast'
+S_WHATSAPP_NET   // '@s.whatsapp.net'
+OFFICIAL_BIZ_JID // '16505361212@c.us'
+META_AI_JID      // '13135550002@c.us'

</file context>


</details>

```

---
Expand Down Expand Up @@ -162,22 +273,34 @@ const sock = makeWASocket({
```typescript
import { areJidsSameUser } from '@whiskeysockets/baileys'

// Works correctly even with device suffixes
// Works correctly with device suffixes — within the same identity form
const isSameUser = areJidsSameUser(
msg.key.participant, // e.g. '19999999999:3@s.whatsapp.net'
sock.user?.id // e.g. '19999999999:0@s.whatsapp.net'
)
```

### Deduplicating PN and LID for the same user

```typescript
import { isLidUser } from '@whiskeysockets/baileys'

async function canonicalLid(jid: string): Promise<string> {
if (isLidUser(jid)) return jid
const lid = await sock.signalRepository.lidMapping.getLIDForPN(jid)
return lid ?? jid // fall back to the original if no mapping exists yet
}
```

---

## What not to do

<Warning>
Never split a JID string with `.split('@')` or compare JIDs with `===`. JIDs may carry device suffixes (`:2`), agent fields, or alternate server domains that cause string comparisons to silently produce wrong results.
Never split a JID string with `.split('@')` or compare JIDs with `===`. JIDs may carry device suffixes (`:2`), agent fields, alternate server domains (`@lid`, `@hosted.lid`), or PN/LID duality that cause string comparisons to silently produce wrong results.

```typescript
// Wrong — misses device variants
// Wrong — misses device variants and ignores PN/LID duality
const user = jid.split('@')[0]
const isSame = jid1 === jid2

Expand Down
32 changes: 30 additions & 2 deletions concepts/socket-config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ sock.ev.on('creds.update', saveCreds)

### `browser`

Controls how Baileys identifies itself to WhatsApp. The type is `WABrowserDescription`, which is a `[platform, browserName, version]` tuple. Use the `Browsers` constant instead of writing the tuple manually.
Controls how Baileys identifies itself to WhatsApp. The type is `WABrowserDescription`, which is a `[platform, browserName, version]` tuple. Use the `Browsers` constant instead of writing the tuple manually — this is also the name that appears in WhatsApp's **Linked Devices** list.

```typescript
import makeWASocket, { Browsers } from '@whiskeysockets/baileys'
Expand All @@ -56,7 +56,35 @@ const sock = makeWASocket({
})
```

The browser you choose affects how much history WhatsApp sends on first sync. Desktop browser identities (macOS or Windows) receive more history than mobile ones.
A few ready-made presets are available:

| Preset | Example |
| --- | --- |
| `Browsers.ubuntu('My App')` | Ubuntu — My App |
| `Browsers.macOS('Desktop')` | macOS — Desktop |
| `Browsers.windows('My App')` | Windows — My App |

The browser you choose also affects how much history WhatsApp sends on first sync. Desktop identities (macOS or Windows) receive significantly more history than mobile ones.

### Receiving full message history

By default, Baileys connects with a Chrome browser profile, which limits how much history WhatsApp delivers on the initial sync. To request the full history, set `syncFullHistory: true` and use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client eligible for extended history.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This wording contradicts the documented default for syncFullHistory and may mislead readers. Clarify that syncFullHistory is already true by default, and that using a desktop browser preset is the key step for extended history.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At concepts/socket-config.mdx, line 71:

<comment>This wording contradicts the documented default for `syncFullHistory` and may mislead readers. Clarify that `syncFullHistory` is already `true` by default, and that using a desktop browser preset is the key step for extended history.</comment>

<file context>
@@ -56,7 +56,35 @@ const sock = makeWASocket({
+
+### Receiving full message history
+
+By default, Baileys connects with a Chrome browser profile, which limits how much history WhatsApp delivers on the initial sync. To request the full history, set `syncFullHistory: true` and use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client eligible for extended history.
+
+```typescript
</file context>
Suggested change
By default, Baileys connects with a Chrome browser profile, which limits how much history WhatsApp delivers on the initial sync. To request the full history, set `syncFullHistory: true` and use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client eligible for extended history.
By default, Baileys connects with a Chrome browser profile, which limits how much history WhatsApp delivers on the initial sync. `syncFullHistory` is `true` by default, but to receive extended history you should use the `Browsers.macOS('Desktop')` preset, which WhatsApp treats as a desktop client.


```typescript
import makeWASocket, { Browsers } from '@whiskeysockets/baileys'

const sock = makeWASocket({
auth: state,
browser: Browsers.macOS('Desktop'),
syncFullHistory: true,
})
```

History messages arrive asynchronously via the `messaging-history.set` event after the connection opens. See [Sync chat history](/advanced/history-sync) for details on consuming the payload.

<Note>
Requesting full history can significantly increase startup time and memory usage on accounts with large chat histories.
</Note>

### `logger`

Expand Down
Loading