WhatsApp Metadata Analysis

Table of Contents

Have you ever wondered what data your device is actually sending to WhatsApp’s servers? I did and looked up some resources online, but I was not entirely satisfied. So, I built a system that captures all my client-to-server traffic from WhatsApp Web and organized it into a structured overview (see the last section of the post).

Image credit: Public Domain Image Archive

GΓ–D’S GATE

πŸ‘‰ If you like this project, then fellow enthusiast, there’s a statistically significant chance you’ll enjoy my sci-fi novel GΓ–D’S GATE πŸ˜„, check it out! πŸ‘ΎπŸ“š

Run it yourself?

If you want to capture the data your own WhatsApp client sends and receives, you can follow this GitHub repo. The key script is test-whatsapp-metadata.js.

Donations?

You can also toss me a coin: Paypal

Introduction: Qualitative Review of WhatsApp’s metadata

The goal of this roject was not only to collect traffic but also to identify which components are protected by end-to-end encryption (E2EE) and which are not. That way, I could reliably distinguish what WhatsApp’s servers can read, and what they never see in plaintext.

A key challenge lies in the layered nature of the encryption. WhatsApp Web communicates using secure WebSockets (wss://) over TLS. Tools such as Wireshark can remove TLS and unmask WebSocket frames (masking in this case is an XOR operation applied to the bits to avoid), but I learned that the underlying payload remains encrypted. This suggests that WhatsApp applies additional application-layer encryption to metadata in addition to Signal-based E2EE of the message content. Evidence of this layering appears clearly in this screenshot:

The frame is unmaskedβ€”you can read the initial β€œWA”—but the rest of the payload is still unreadable (encrypted). This likely represents another encryption layer applied by WhatsApp, which is a sensible security measure.

Inspecting libsignal, Signal’s open-source E2EE library, does not reveal which fields WhatsApp encrypts. The library is a tool, not a policy. Moreover, even though Meta licenses libsignal, WhatsApp does not use the reference implementation directly. Instead, they rely on proprietary libraries that build on top of libsignal and these are not open-source. As a result, only WhatsApp can tell us what’s end-to-end encryptedβ€”and they do (see below).

What Is Encrypted?

Based on WhatsApp’s public documentation: message contentβ€”including text, media, voice messages, documents, live location, status updates, and callsβ€”is fully end-to-end encrypted. This means Meta cannot access this content in plaintext once it leaves the sender’s device.

What Metadata Is Not Encrypted?

Metadata = “data about data”

While WhatsApp uses end-to-end encryption (E2EE) for message content, they still collect metadata about your communications.

Metadata necessary for routing and service operation is not E2EE-protected. This typically includes (among others):

  • Phone numbers
  • Message timestamps
  • Frequency and volume of communication
  • Device information
  • IP address
  • Sender and recipient (internal) identifiers
  • Group membership information
  • Profile data
  • Non-E2EE backups
  • Payment transaction data (in regions where payments are supported)

This list aligns with independent analyses such as those from Wire, a messaging platform that also implements E2EE (via MLS, an alternative to Signal that scales better for group chats). Wire similarly notes that backups are not end-to-end encrypted.

Online I read debate about two contentious data points though: user location and URLs sent in a message. Can Meta servers read them? I investigated further.

Location

Regarding location, in WhatsApp’s website it reads: “When end-to-end encrypted, your messages, photos, videos, voice messages, documents, live location, status updates, and calls are secured from falling into the wrong hands.”.

However, Signal’s president, Whittaker, in this article, asserts that “WhatsApp licenses Signal’s end-to-end encryption technology. Nevertheless, a lot of personal and intimate information isn’t protected. […] According to Signal’s president, this involves users’ location data, contact lists, when they send someone a message, when they stop, what users are in their group chats, their profile picture, and much more.”

So there are different possibilities to interpret her words:

  1. Since WhatsApp indicates that locaton is E2EE, Whittaker may have expressed the point too broadly.
  2. Whittaker refers to location derived from IP, which WhatsApp acknowledges using primarily for spam detection see a talk from M. Jones, a SW Engineer at WhatsApp that led the anti-spam effort after E2EE was enabled. Signal uses other methods to prevent spam, see this Signal post.
  3. Whittaker really refers to GPS location when we share it in a WhatsApp message. However, WhatsApp states clearly in their website that “live location” is not shared. But perhaps static location is shared with WhatsApp servers.

Wire’s analysis provides an additional signal: it explicitly lists location sharing among the types of content protected by E2EE, and omits location from its enumeration of metadata accessible to WhatsApp servers. Given that Wire competes with WhatsApp, this omission is notableβ€”if they believed location was exposed, they would likely point it out.

Based on the available evidence, the most consistent interpretation of Whittaker’s words is (2): the β€œlocation data” referenced is IP-based, not GPS location shared in chats.

URLs

Link previews offer useful insight into URL handling. Evidence suggests that WhatsApp clients, not WhatsApp servers, fetch preview data from third-party servers hosting the website. See this Stack Exchange analysis.

  • The client requests the URL directly.
  • The target server therefore receives the user’s IP.

WhatsApp provides an option to disable link previews, explicitly describing this as a privacy measure to protect the user’s IP address: “As an advanced security measure, you can turn on Disable link previews. If you turn on Disable link previews, the links you send to others won’t generate a link preview. This is an optional layer of security to protect your IP address”.

Additional security writeups and discussions (e.g., TheHackerNews, Hacker News threads) explain that receiving a URLβ€”even without clicking itβ€”can expose the recipient’s IP through preview generation.

A practical tip: Another reason to add a space after a URL is to prevent any appended text from being interpreted as part of the link, which would cause a third-party server to fetch it and potentially expose unintended message content.

Overall, URLs appear to be end-to-end encrypted. The server cannot read them unless the link points to a Meta-owned domain and Meta correlates timestamps across servicesβ€”something for which no evidence has been presented. WhatsApp states that data shared with Facebook is limited to operational areas such as spam detection, not advertising (see this Wired articleβ€”note that’s the magazine, not the messaging app).


This concludes the qualitative overview. The next sections of the post shows how I captured WhatsApp Web traffic and what the decrypted (post-TLS and post-masking) payload looks like. While this does not reveal which fields are E2EE-protected internally, it provides a complete unencrypted view of the data exchanged between the client and WhatsApp’s servers.

Analysis with Wireshark - Removing TLS encryption

After removing the TLS layer, performing a man-in-the-middle attack on myself, this is what I could see, and hence Meta servers are guaranteed to see.

πŸ”΄ Without any decryption

  1. Connection Metadata:

    • WebSocket connection establishment to web.whatsapp.com
    • Connection timing (when you connect/disconnect)
    • Server IP addresses
  2. Network Routing:

    • Your IP address
    • TCP/TLS handshake metadata

πŸ”΄ After TLS Decryption:

  1. Device Information (HTTP Headers):

    • User-Agent string (OS version, browser version)
    • Browser capabilities (WebSocket support, compression)
    • Language preference
  2. Session Data (HTTP Headers):

    • Session cookies (wa_ul, wa_web_lang_pref)
    • Authentication tokens
  3. WebSocket Connection Data

    • The contents of the WebSocket handshake and all messages sent back and forth after it’s established. But the WebSocket payload was encrypted, see image in the introduction (and example below).

HTTP WebSocket Handshake (decrypted):

GET /ws/chat HTTP/1.1
Host: web.whatsapp.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) ...
Cookie: wa_ul=77d37bf9-2948-47de-8dfc-3d5e4fafb884

πŸ‘† Visible to servers - only connection metadata

WebSocket Binary Payload (after TLS decryption):

πŸ‘† Still encrypted - contains message content AND metadata

🟒 What Remains Encrypted (Even After TLS Decryption):

  1. Message Content: Text, media, documents, voice notes.

  2. Message Metadata:

    • Phone numbers (sender, recipient)
    • Message IDs
    • Timestamps (when message was sent)
    • Contact information
    • Group membership
    • Reaction data
    • Reply/forward metadata
    • All fields captured by test-whatsapp-metadata.js

Test Script Walkthrough: What Data Points Are Visible and Where

This section walks through the metadata collection test script (test-whatsapp-metadata.js), and based on the qualitative investigation I did at the top of the post, what data is collected and where it’s processed (Meta’s servers vs. your device).

Running the Metadata Inspector

After setting up your WhatsApp Web client, test exactly what metadata is captured:

node test-whatsapp-metadata.js

Then send yourself different types of messages to see the output.

Decrypting the full payload on the client side

After examining the payload following TLS decryption, I then focused on enumerating all data exchanged by WhatsApp clients. While this method cannot determine which specific fields are protected by E2EE (a rooted device might allow deeper inspection), it does reveal the full set of data transmitted between client and server. Note that some of these data points are visible to Meta, while othersβ€”such as the content types discussed earlierβ€”are protected by end-to-end encryption. Thus, the following is an informed assessment of what could and could not be seen by Meta.

Legend for this walkthrough:

  • πŸ”΄ SERVER-SIDE: Meta’s servers have direct access to this data
  • 🟑 CALCULATED SERVER-SIDE: Meta could calculate/infer this from server logs
  • 🟒 CLIENT-SIDE ONLY: Only visible on your device (E2E encrypted or local-only)

Metadata visible to Meta (based on the qualitative investigation)

This section corresponds to what Meta’s servers could potentially see, log, and analyze.

The test script uses the message_create event, which captures every message (incoming and outgoing).

The event produces a message object: “msg”, which has a series of properties that I can inspect.

Communication Patterns

Phone Numbers πŸ”΄ SERVER-SIDE

msg.from.split('@')[0]  // Your phone number
msg.to.split('@')[0]    // Contact's phone number
  • How Meta could get it: Every message packet includes sender/recipient IDs in routing headers
  • Format: [email protected] (personal) or [email protected] or 123-456@lid (groups)
  • Privacy impact: Can map your entire social graph (who talks to whom)

Timestamps πŸ”΄ SERVER-SIDE

new Date(msg.timestamp * 1000).toISOString()
  • How Meta could get it: Message creation timestamp in every packet header
  • Precision: Down to the second
  • Privacy impact: Can build behavioral profiles (sleep schedule, work hours, activity patterns)

Message Volume 🟑 CALCULATED SERVER-SIDE

const messageCount = allMessages.length;
  • How Meta could track it: Server logs accumulate total message count per conversation
  • Privacy impact: Shows conversation depth over time
  • What the script does: Fetches all messages to demonstrate total count

Communication Frequency 🟑 CALCULATED SERVER-SIDE

How I could calculate it:

const chat = await msg.getChat();
const allMessages = await chat.fetchMessages({ limit: 99999 });
...
const avgPerDay = (messageCount / daysDiff).toFixed(2);
  • How Meta could calculate:
    • Logs all message timestamps server-side
    • Calculates: total_messages / days_between_first_and_last
  • Privacy impact: Identifies relationship intensity (high frequency = close friend)
  • What the script does: Demonstrates this calculation by fetching history and computing average

Group Memberships πŸ”΄ SERVER-SIDE (for groups only)

if (isGroup) {
  chat.name                  // Group name
  chat.id._serialized       // Group ID
  chat.participants.length  // Member count
}
  • How Meta could get it: Server maintains group membership tables for routing
  • Privacy impact: Can build interest graphs (political groups, hobby groups, etc.)

Group Lifecycle Events πŸ”΄ SERVER-SIDE (for groups only)

chat.createdAt  // Group creation timestamp
  • How Meta could get it: Server records all group create/delete/join/leave events
  • Privacy impact: Tracks group dynamics over time

Device Information

Device Type πŸ”΄ SERVER-SIDE

msg.deviceType  // "android", "ios", "web"
  • How Meta coudl get it: Message packets include device type identifier
  • Note: This shows the primary device (usually phone), not linked devices
  • Privacy impact: Device fingerprinting

Operating System (User-Agent) πŸ”΄ SERVER-SIDE

await getOSInfo()  // Extracts from User-Agent string <- this is simulated on my script. WhatsApp's web server gets it from the HTTP header
  • How Meta could get it: HTTP “User-Agent” header in every request
  • Example: “Mozilla/5.0 (Windows NT 10.0) Chrome/120.0.0.0”
  • Privacy impact: Detailed OS version reveals device fingerprint

WhatsApp Version πŸ”΄ SERVER-SIDE

await getWhatsAppVersion()  // From window.Debug.VERSION
  • How Meta could get it: Client sends version in connection handshake
  • Privacy impact: Used for forced updates, compatibility checks

Device ID (WhatsApp ID) πŸ”΄ SERVER-SIDE

info.wid._serialized  // e.g., "[email protected]"
  • How Meta could get it: Assigned during registration, permanent identifier
  • Privacy impact: Links all your activity together across sessions

IP Address πŸ”΄ SERVER-SIDE (automatic)

const ipInfo = await getPublicIP();  //  script uses ipapi.co for **demo**
log(`Your IP address: ${ipInfo.ip}`);
  • How Meta could get it:
    • Automatic - every TCP/IP connection includes source IP in packet headers
    • No special code needed - built into internet protocol
    • Server logs automatically record connecting IP
  • What the script does: Calls ipapi.co API to demonstrate what info can be derived
  • Privacy impact:
    • Approximate location (city-level accuracy)
    • ISP identification
    • Track network changes (WiFi β†’ cellular)
    • Used for spam filtering

However, WhatsApp is very explicit that they only use IPs for spam detection.

Geographic Location 🟑 CALCULATED SERVER-SIDE (from IP)

log(`Location: ${ipInfo.city}, ${ipInfo.region}, ${ipInfo.country}`); //  script uses ipapi.co for **demo**
  • How could location be calculated:
    • IP address β†’ GeoIP database lookup (MaxMind, IP2Location, etc.) This one is free.
    • These databases map IP ranges to physical locations
    • Accuracy: typically city-level, sometimes neighborhood
  • Our script does: Uses ipapi.co to demonstrate geolocation lookup
  • Privacy impact: If missued.
    • Track where you are when messaging
    • Build travel patterns
    • Identify home/work locations (frequent IPs)

ISP/Network Operator 🟑 CALCULATED SERVER-SIDE (from IP)

log(`ISP/Network: ${ipInfo.org || 'Unknown'}`); //  script uses ipapi.co for **demo**
  • How could ISP be determined:
    • IP WHOIS lookup (public information)
    • Organization that owns the IP block
  • What the script does: Uses ipapi.co to show ISP info
  • Privacy impact:
    • Reveals which network (home ISP vs mobile vs WiFi)
    • Corporate WiFi could reveal employer
    • Public WiFi reveals venue (coffee shop, airport)

Full disclosure: I could not make this one work, even when I was sending WhatsApp messages with ISP data, not wifi. I haven’t been able to debug yet.

Language Settings πŸ”΄ SERVER-SIDE

Intl.DateTimeFormat().resolvedOptions().locale  // This one is for demo purposes
  • How Meta could get it:
    • HTTP “Accept-Language” header (automatic with every request)
    • Example: Accept-Language: en-US,en;q=0.9,es;q=0.8
  • Our script does: Reads browser locale to demonstrate
  • Privacy impact: Language/culture preferences, user fingerprinting

Timezone 🟑 CALCULATED SERVER-SIDE

Intl.DateTimeFormat().resolvedOptions().timeZone  //  **demo**
  • How could timezone be determined:
    • Can infer from message timestamps vs server time
  • What the script does: Reads browser timezone to demonstrate
  • Privacy impact:
    • Another location indicator (potentially narrows down where you are)
    • Reveals travel (timezone changes)

Account Activity

Last Seen Timestamp πŸ”΄ SERVER-SIDE

new Date(msg.timestamp * 1000).toISOString()
  • How Meta could observe: From message timestamps
  • Privacy impact: Activity patterns

Online/Offline Status πŸ”΄ SERVER-SIDE

// Not in message object, but Meta could track via WebSocket connections
  • How Meta could track:
    • WebSocket connection = online
    • Disconnection = offline
    • Server logs connection/disconnection timestamps
  • Proof: WhatsApp shows “online” indicator to other users
  • Privacy impact: Session duration, online patterns, most active hours

Profile Picture URL πŸ”΄ SERVER-SIDE

const profilePicUrl = await contact.getProfilePicUrl();
  • How Meta could access it:
    • All profile pictures hosted on Meta’s Content Delivery Network (pps.whatsapp.net)
    • Returns null if privacy settings hide it
  • Privacy impact: Meta could track profile picture changes, metadata from uploads

Linked Devices πŸ”΄ SERVER-SIDE (

// Not in message API, but proven by "Linked Devices" in WhatsApp's UI

WhatsApp Settings shows “Linked Devices” list. It shows device names, types, “Last active” time.

  • What could be tracked:
    • Number of linked devices
    • Device types (Web, Desktop, Portal)
    • Link timestamps
    • Last active time per device
  • Privacy impact: Full device ecosystem visibility

Message Metadata (NOT content)

Message ID πŸ”΄ SERVER-SIDE

msg.id._serialized
  • Format: "[email protected]_3EB0C8F4E2D5A1B6C3D4E5F6"
  • Components:
    • true/false: direction (sent/received)
    • Phone ID: participant
    • Random string: unique identifier
  • Privacy impact: Every message permanently tracked for delivery, deletion logging

Timestamps (Sent/Delivered/Read) πŸ”΄ SERVER-SIDE

msg.timestamp  // When sent
msg.ack        // Delivery status (0-4)
  • How Meta could track: Server logs each stage of message pipeline
  • Ack values:
    • 0: Sent from device
    • 1: Delivered to server
    • 2: Delivered to recipient
    • 3: Read by recipient
    • 4: Played (voice/video)
  • Privacy impact: Complete delivery pipeline visibility, read receipts

Delivery Status πŸ”΄ SERVER-SIDE

getAckStatus(msg.ack)
  • How Meta could track: Server maintains delivery state for each message
  • Privacy impact: Knows when recipient reads your message

Forward Count πŸ”΄ SERVER-SIDE

msg.isForwarded          // boolean
msg.forwardingScore      // times forwarded
  • How Meta could track: Server increments counter each time message is forwarded
  • Privacy impact:
    • Track viral content
    • Identify misinformation/spam
    • Enforce forwarding limits (5 chats at once)

Media Type πŸ”΄ SERVER-SIDE

msg.type  // "chat", "image", "video", "audio", "ptt", "document", etc.
msg.hasMedia
  • How Meta could get it: Message type in packet header
  • Privacy impact:
    • Know you sent media (but not content)
    • Compression decisions

File Size πŸ”΄ SERVER-SIDE (approximate)

if (msg.hasMedia) {
  const media = await msg.downloadMedia();
  const sizeKB = Math.round(media.data.length * 0.75 / 1024);
}
  • How Meta could get it: Encrypted file size when uploaded to CDN
  • Our script does: Downloads and calculates size from base64 length
  • Privacy impact:
    • Storage tracking
    • File size hints at content type (large = video, small = photo)

Call Metadata

Call Detection πŸ”΄ SERVER-SIDE

if (msg.type === 'call_log') {
  // Call metadata
}
  • What Meta could log:
    • Caller/callee IDs
    • Call timestamps (start/end)
    • Call duration
    • Call type (voice/video)
    • Missed/declined/canceled status
  • Note: Call AUDIO/VIDEO is E2E encrypted, but METADATA is not
  • Privacy impact:
    • Who you call and how often
    • Call patterns
    • Voice vs video preference

Group-Specific Metadata (for group chats)

Group Identification πŸ”΄ SERVER-SIDE

chat.id._serialized  // Permanent group ID
chat.name            // Group name (can change)
  • How Meta could track: Server maintains group registry
  • Privacy impact: Group affiliations visible

Participant List πŸ”΄ SERVER-SIDE

chat.participants.length              // Member count
chat.participants.filter(p => p.isAdmin)  // Admin list
  • How Meta could access: Server maintains membership table for message routing
  • Privacy impact:
    • Social graph within groups
    • Group size reveals nature (small = private, large = public)
    • Admin identification

Message Author (in groups) πŸ”΄ SERVER-SIDE

msg.author || msg.from  // Who sent this message in the group
  • How Meta could track: Message packets include author ID
  • Privacy impact:
    • Meta knows who says what in groups
    • Can analyze: most active members, influence patterns

Mentions πŸ”΄ SERVER-SIDE

msg.mentionedIds  // Array of @mentioned user IDs
  • How Meta could track: Mention metadata in message packet
  • Privacy impact:
    • Social connections within groups
    • Frequent mentions = close relationships
    • Mention patterns reveal subgroups/cliques

Data not visible to Meta (E2EE)

This section shows data protected by E2E encryption (Signal Protocol), which has been informed by the qualitative assessment.

πŸ” Message Content (E2E Encrypted)

Message Text 🟒 CLIENT-SIDE ONLY

msg.body  // The actual text content
  • Why we can see it: We’re the recipient; our client has the decryption key
  • Why Meta can’t see it:
    • Servers only see encrypted ciphertext
    • Decryption key never leaves your device
    • Signal Protocol prevents server decryption
  • What Meta sees: "A8f3kJ9sP2mX7qL..." (base64-encoded ciphertext)

Message Length 🟒 CLIENT-SIDE ONLY

msg.body.length  // Character count of DECRYPTED text
  • What we see: Actual character count (e.g., 50 characters)
  • What Meta sees: Encrypted payload size in BYTES, not character count
  • Important:
    • Encryption adds padding, so bytes β‰  character count
    • Example: “Hi” (2 chars) might be 256 bytes encrypted
    • Meta CANNOT determine exact character count from encrypted size

Media Content 🟒 CLIENT-SIDE ONLY

if (msg.hasMedia) {
  const media = await msg.downloadMedia();
  // media.mimetype, media.filename, media.data
}
  • How it works:
    1. Media encrypted on sender’s device
    2. Encrypted file uploaded to WhatsApp CDN
    3. Encryption key sent in E2E encrypted message
    4. Recipient downloads encrypted file
    5. Recipient decrypts using key
  • What Meta sees:
    • Encrypted file blob (gibberish)
    • File size, mime type, timestamps
  • What Meta CAN’T see:
    • Actual image/video content.
    • Text in documents
    • Audio in voice messages

Voice Messages 🟒 CLIENT-SIDE ONLY

if (msg.type === 'ptt' || msg.type === 'audio') {
  // Voice message audio is E2E encrypted
}
  • Meta cannot transcribe or listen
  • No server-side speech recognition
  • Can’t be used for voice fingerprinting

Quoted Message Content 🟒 CLIENT-SIDE ONLY

if (msg.hasQuotedMsg) {
  const quoted = await msg.getQuotedMessage();
  quoted.body  // Encrypted
}

Payload sent to WhatsApp servers


πŸ’‘ WHAT THE BELOW JSON SHOWS: βœ… COMPLETE message object (all properties) βœ… COMPLETE chat object (all properties) βœ… Internal _data structures (raw protocol data) βœ… This is what WhatsApp Web servers receive from the client. Some of it will be E2EE, as discussed above, and others will be in plain text to Meta’s servers.

I had ChatGPT give a definition for each of the data points.

═══════════════════════════════════════════════════════════════ MESSAGE OBJECT (msg): ═══════════════════════════════════════════════════════════════

KeyExample (Anonymized Value)Description
_data.id.fromMetrueIndicates if the message was sent by the current user.
_data.id.remote[email protected]Group identifier (the chat’s unique WhatsApp JID).
_data.id.idMSG_ABC123XYZ789Unique message identifier generated by WhatsApp.
_data.id.participantuser_5550001234@lidThe participant’s WhatsApp ID within the group.
_data.id._serialized[email protected]_MSG_ABC123XYZ789_user_5550001234@lidSerialized string combining sender, group, and message IDs.
_data.viewedfalseWhether the message has been viewed by the user.
_data.body"Test group message"Message content.
_data.type"chat"Type of message (chat, image, video, etc.).
_data.t1762705578Unix timestamp when the message was created.
_data.clientReceivedTsMillis1762705577584Timestamp when client received the message.
_data.notifyName"Alex Doe"Display name of the sender.
_data.from"user_5550001234@lid"Sender’s WhatsApp ID.
_data.to"[email protected]"Recipient group’s ID.
_data.author"user_5550001234@lid"Author of the message in group context.
_data.ack1Acknowledgement state (1 = delivered to server).
_data.isNewMsgtrueWhether the message is newly received.
_data.viewMode"VISIBLE"Whether message is visible to the user.
_data.messageSecret{ "0": 228, "1": 255, ... }Internal encryption metadata for message authentication.
_data.productHeaderImageRejectedfalseFlag for business message media validation.
_data.links[]List of detected URLs in message (if any).
invisfalseIndicates whether the message is invisible in the chat (e.g., background system messages).
isNewMsgtrueMarks the message as newly received by the client.
starfalseWhether the message has been starred/bookmarked by the user.
kicNotifiedfalseInternal flag used when key integrity checks trigger notifications.
recvFreshtrueDenotes that the message was freshly received rather than restored from backup or cache.
isFromTemplatefalseIndicates if the message originated from a pre-defined template (used in business messaging).
isAdsMediafalseShows whether this message carries advertising media content.
pollInvalidatedfalseTrue if a poll message became invalid (e.g., after an edit or deletion).
isSentCagPollCreationfalseFlag marking a “Create-A-Group poll” message type.
latestEditMsgKeynullIf message was edited, stores the message key of the last edit.
latestEditSenderTimestampMsnullTimestamp in milliseconds of the last message edit.
mentionedJidList[]List of user IDs mentioned in the message. Empty here.
groupMentions[]Mentions of entire groups (if group tags like @everyone were used).
isEventCanceledfalseUsed in event-type messages to denote cancellation.
eventInvalidatedfalseInternal invalidation flag for event messages.
isVcardOverMmsDocumentfalseIndicates if a contact card (vCard) was sent as an MMS-style document.
isForwardedfalseTrue if the message was forwarded from another chat.
isQuestionfalseMarks messages that represent user questions in interactive features.
questionReplyQuotedMessagenullIf replying to a question, this references the quoted question message.
questionResponsesCount0Count of responses received for a question-type message.
readQuestionResponsesCount0Count of question responses that have been read by the sender.
hasReactionfalseIndicates whether someone reacted (emoji reaction) to this message.
viewMode"VISIBLE"Specifies visibility state (e.g., VISIBLE, HIDDEN, ARCHIVED).
messageSecret{ "0": 228, "1": 255, ... "31": 168 }32-byte array containing encryption/authentication key fragments for message verification.
productHeaderImageRejectedfalseUsed for business messages β€” shows if a product header image was rejected by validation.
lastPlaybackProgress0Tracks progress for audio/video message playback.
isDynamicReplyButtonsMsgfalseIndicates if message includes interactive reply buttons.
isCarouselCardfalseMarks a business-style carousel message (multi-card layout).
parentMsgIdnullReferences the parent message in threads/replies.
callSilenceReasonnullDescribes why a call was silenced (if message relates to a call).
isVideoCallfalseMarks if the message relates to a video call event.
callDurationnullDuration of the call, if applicable.
callCreatornullWhatsApp ID of the user who initiated the call.
callParticipantsnullList of participants involved in a call message.
isCallLinknullMarks if message represents a call link invite.
callLinkTokennullUnique token for a generated call link.
isMdHistoryMsgfalseUsed internally for message synchronization history events.
stickerSentTs0Timestamp for sticker-sent messages.
isAvatarfalseTrue if the message involves an avatar (custom emoji or profile sticker).
lastUpdateFromServerTs0Timestamp of the last server update for this message.
invokedBotWidnullID of the bot invoked, if message triggered a bot response.
bizBotTypenullType/category of business bot (if used).
botResponseTargetIdnullMessage ID to which the bot is responding.
botPluginTypenullPlugin type used by the bot for message automation.
botPluginReferenceIndexnullNumeric reference to plugin action performed by the bot.
botPluginSearchProvidernullProvider used for in-message search (e.g., Yelp, Google).
botPluginSearchUrlnullURL used by the bot for query-based responses.
botPluginSearchQuerynullQuery string the bot executed.
botPluginMaybeParentfalseIndicates if this bot message is a parent in a thread chain.
botReelPluginThumbnailCdnUrlnullCDN link to a thumbnail image for bot reel messages.
botMessageDisclaimerTextnullDisclaimer text appended to bot messages.
botMsgBodyTypenullType of bot message body (e.g., text, card, media).
reportingTokenInfonullToken info used for abuse/spam reporting flow.
requiresDirectConnectionnullIf true, indicates message required direct socket transmission.
bizContentPlaceholderTypenullPlaceholder type for business content templates.
hostedBizEncStateMismatchfalseIndicates encryption mismatch in business message hosting.
senderOrRecipientAccountTypeHostedfalseMarks if sender/recipient is a hosted business account.
placeholderCreatedWhenAccountIsHostedfalseTrue if placeholder was created due to hosted account sync.
groupHistoryBundleMessageKeynullKey reference for message bundles in group history.
groupHistoryBundleMetadatanullAdditional metadata about bundled messages.
links[]List of hyperlinks found in message text, if any.

═══════════════════════════════════════════════════════════════ CHAT OBJECT (chat): ═══════════════════════════════════════════════════════════════

chat β”œβ”€β”€ groupMetadata β”œβ”€β”€ id β”œβ”€β”€ name β”œβ”€β”€ lastMessage β”‚ β”œβ”€β”€ _data (raw message payload) β”‚ └── normalized message object (shown above) └── client (active WhatsApp session reference)

Group Metadata

KeyExample (Anonymized Value)Description
groupMetadata.id.server"g.us"Identifies the type of chat β€” "g.us" denotes a WhatsApp group.
groupMetadata.id.user"120300000000001"The numeric group identifier assigned by WhatsApp.
groupMetadata.id._serialized"[email protected]"Combined string representing the group’s unique JID (Jabber ID).
groupMetadata.creation1762616768Timestamp representing when the group was created (UNIX epoch time).
groupMetadata.owner.server"lid"Server domain for the group owner (typically linked device or business).
groupMetadata.owner.user"17310000000000"Owner’s WhatsApp user ID.
groupMetadata.owner._serialized"17310000000000@lid"Full JID of the group’s owner.
groupMetadata.subject"Stellar Gateway"The group’s display name.
groupMetadata.subjectTime1762616778Timestamp when the group subject/title was last updated.
groupMetadata.descTime0Timestamp for the group description update; 0 means no description set.
groupMetadata.restrictfalseIf true, only admins can send messages.
groupMetadata.announcefalseIf true, only admins can post announcements.
groupMetadata.noFrequentlyForwardedfalseIf true, restricts forwarding messages often flagged as β€œfrequently forwarded.”
groupMetadata.ephemeralDuration0Message disappearing time (seconds); 0 disables ephemeral messages.
groupMetadata.disappearingModeTrigger"chat_settings"Source that triggered the disappearing messages mode (e.g., manual settings).
groupMetadata.disappearingModeInitiatedByMefalseIndicates if the user initiated disappearing mode.
groupMetadata.membershipApprovalModefalseWhether new members require approval before joining.
groupMetadata.memberAddMode"all_member_add"Defines who can add members (e.g., all members or admins only).
groupMetadata.memberLinkMode"admin_link"Defines who can generate and share the invite link.
groupMetadata.reportToAdminModefalseEnables β€œreport to admin” functionality if true.
groupMetadata.size3Total number of current members.
groupMetadata.supportfalseTrue for official support or service groups (used by WhatsApp Business).
groupMetadata.suspendedfalseMarks the group as suspended by system moderation.
groupMetadata.terminatedfalseMarks the group as deleted/terminated by WhatsApp.
groupMetadata.uniqueShortNameMap{}Mapping of unique short aliases (if the group has a short invite name).
groupMetadata.isLidAddressingModetrueIndicates usage of the new linked-device addressing mode.
groupMetadata.isParentGroupfalseMarks a group as a parent in a community structure.
groupMetadata.isParentGroupClosedfalseIndicates if a parent group is closed for new subgroups.
groupMetadata.defaultSubgroupfalseIdentifies if this is the default subgroup of a parent community.
groupMetadata.generalSubgroupfalseMarks general-purpose subgroup in a community.
groupMetadata.groupSafetyCheckfalseUsed internally for WhatsApp’s group integrity verification.
groupMetadata.generalChatAutoAddDisabledfalseIf true, disables auto-adding members to the group.
groupMetadata.allowNonAdminSubGroupCreationfalseControls whether non-admins can create subgroups.
groupMetadata.lastActivityTimestamp0Tracks last message or update activity timestamp.
groupMetadata.lastSeenActivityTimestamp0Timestamp when group activity was last observed by the client.
groupMetadata.incognitofalseMarks the group as hidden or stealth (for privacy testing).
groupMetadata.hasCapifalseIndicates whether the group has Cloud API integration (for business use).

Participant List

Participant (Anonymized)AdminSuper AdminDescription
[email protected]falsefalseRegular member of the group.
[email protected]truetruePrimary admin or group creator.
[email protected]falsefalseRegular participant.

Additional Group Metadata

KeyExample (Anonymized Value)Description
pendingParticipants[]List of users awaiting approval to join the group.
pastParticipants[]Users who have left or been removed from the group.
membershipApprovalRequests[]Pending membership requests for approval-required groups.
subgroupSuggestions[]Suggested subgroups under a community umbrella.

Chat ID Block

KeyExample (Anonymized Value)Description
id.server"g.us"Server suffix for group chats.
id.user"120300000000001"Group’s unique numerical identifier.
id._serialized"[email protected]"Combined JID string identifying the chat.

Chat-Level Properties

KeyExample (Anonymized Value)Description
name"Stellar Gate"The display name of the chat or group.
isGrouptrueConfirms that the chat type is a group conversation (false would mean private chat).
isReadOnlyfalseIf true, the current user cannot send messages (e.g., view-only or announcement group).
unreadCount0The number of unread messages in this chat.
timestamp1762634551Last activity timestamp in UNIX format (seconds).
archivedfalseWhether the chat is archived in the WhatsApp UI.
pinnedfalseIndicates whether the chat is pinned to the top of the chat list.
isMutedfalseWhether notifications for this chat are muted.
muteExpiration0If muted, this holds the mute expiration time (0 means not muted).

lastMessage._data β€” Message Metadata Breakdown

KeyExample (Anonymized Value)Description
id.fromMetrueIndicates the message was sent by the current user.
id.remote"[email protected]"JID (Jabber ID) of the group chat this message was sent to.
id.id"3A82CCB4F9B25A74BFBA"The unique message identifier assigned by WhatsApp.
id.participant._serialized"17310000000000@lid"Sender’s user ID within the group (Linked Device context).
id._serialized"[email protected]_3A82CCB4F9B25A74BFBA_17310000000000@lid"A combined encoded message key used internally by WhatsApp.
viewedfalseMessage not yet viewed by the sender’s device.
body"TEST - Group message"Actual text content of the message.
type"chat"Message type β€” can be chat, image, video, audio, etc.
t1762705578Message creation timestamp (UNIX).
clientReceivedTsMillis1762705577584Timestamp (ms) when the client received confirmation.
notifyName"Alex M."Display name of the sender (from their contact info or profile).
from._serialized"17310000000000@lid"Sender’s JID (Linked Device format).
to._serialized"[email protected]"Receiver’s JID (the group).
author._serialized"17310000000000@lid"Author JID (in groups, identical to from).
ack1Message delivery acknowledgment (0–4 scale: sent, delivered, read, etc.).
invisfalseInternal visibility flag (used for hidden messages).
isNewMsgtrueMarks the message as newly received or created.
starfalseIf true, this message has been starred by the user.
kicNotifiedfalseInternal flag for β€œkeep in chat” notification (used for disappearing messages).
recvFreshtrueMarks message as freshly received from the server.
isFromTemplatefalseIndicates whether message came from a pre-defined template (used in business chats).
isAdsMediafalseIdentifies if message contains media linked to an ad.
pollInvalidatedfalseIf a poll message was invalidated or retracted.
isSentCagPollCreationfalseInternal flag for poll creation status.
latestEditMsgKeynullReference to the most recent edited version (if message was edited).
latestEditSenderTimestampMsnullTimestamp of last edit made by the sender.
mentionedJidList[]List of JIDs mentioned in this message (@mentions).
groupMentions[]List of group mentions (e.g., @everyone).
isEventCanceledfalseUsed for event-based messages (e.g., group call, poll event) that got canceled.
eventInvalidatedfalseMarks an event as invalidated.
isVcardOverMmsDocumentfalseTrue if the message includes a vCard file shared over MMS.
isForwardedfalseIndicates if the message has been forwarded.
isQuestionfalseMarks if this is an interactive question-type message.
questionReplyQuotedMessagenullHolds quoted question data (if replying to one).
questionResponsesCount0Number of responses to this question-type message.
readQuestionResponsesCount0Number of responses viewed by the current user.
hasReactionfalseIndicates if the message has emoji reactions attached.
viewMode"VISIBLE"Whether message is visible to the user.

messageSecret (Encryption Metadata)

{ “0”: 228, “1”: 255, “2”: 255, …, “31”: 168 }

Extended Metadata Flags

KeyExampleMeaning
productHeaderImageRejectedfalseFor business/media messages, indicates if product header image failed validation.
lastPlaybackProgress0Tracks audio/video playback progress.
isDynamicReplyButtonsMsgfalseTrue for messages containing interactive quick-reply buttons.
isCarouselCardfalseMarks media cards (used in business catalog messages).
parentMsgIdnullReference to a parent message (for replies).
isVideoCallfalseIndicates if this message represents a video call record.
callDurationnullDuration of a call, if message is a call log.
isMdHistoryMsgfalseMarks messages synchronized from multi-device message history.
isAvatarfalseUsed when message includes an avatar sticker.
invokedBotWidnullBot identifier, if message triggers an automated bot response.
botPluginTypenullIdentifies a bot plugin type, if applicable.
requiresDirectConnectionfalseWhether message delivery requires direct peer connection (e.g., call setup).
hostedBizEncStateMismatchfalseInternal flag for business encryption mismatches.
senderOrRecipientAccountTypeHostedfalseTrue if either side uses a hosted WhatsApp Business account.
placeholderCreatedWhenAccountIsHostedfalseWhether placeholder messages were generated for hosted accounts.
groupHistoryBundleMessageKeynullReference key for historical bundle sync.
groupHistoryBundleMetadatanullMetadata for history bundle messages.
links[]Array of detected hyperlinks in message text.
id.fromMetrueIndicates that the message was sent by the current user.
id.remote"[email protected]"The destination group JID (unique chat identifier).
id.id"3A82CCB4F9B25A74BFBA"The unique WhatsApp message ID assigned to this message.
id.participant._serialized"17310000000000@lid"Identifies the message sender (even inside group messages).
id._serialized"[email protected]_3A82CCB4F9B25A74BFBA_17310000000000@lid"Full serialized message key combining sender, chat, and message ID.
ack1Message acknowledgment status:
β€’ 0: pending
β€’ 1: delivered to server
β€’ 2: delivered to recipient
β€’ 3: read
β€’ 4: played (for audio/video).
hasMediafalseIndicates that this message contains no media (plain text only).
body"TEST - Group message"The text content of the message.
type"chat"Message type β€” "chat" for text, "image" for pictures, "audio", "video", "ptt", etc.
timestamp1762705578Unix timestamp when message was created.
from"17310000000000@lid"Sender’s WhatsApp ID.
to"[email protected]"Receiver’s WhatsApp ID β€” in this case, the group JID.
author"17310000000000@lid"In group chats, specifies the message’s author (often same as from).
deviceType"ios"Indicates that the sender’s device was an iOS device.
isForwardedfalseMessage was not forwarded.
forwardingScore0Internal metric showing how many times a message has been forwarded.
isStatusfalsetrue when message belongs to a user’s status update.
isStarredfalseIndicates whether message is starred/bookmarked by the user.
fromMetrueConfirms that this message was sent by the local account (duplicate of id.fromMe).
hasQuotedMsgfalseWhether this message includes a quoted message reference (reply).
hasReactionfalseIndicates presence of emoji reactions on this message.
vCards[]Holds shared contact cards (vCards) β€” empty for this message.
mentionedIds[]Array of JIDs mentioned using @username syntax.
groupMentions[]Marks if @everyone or @admins tags were used.
isGiffalsetrue if media is an animated GIF.
links[]Parsed list of URLs found in message text.
client"[Client Reference - Omitted]"Internal handle linking to the active WhatsApp Web client instance.

═══════════════════════════════════════════════════════════════ RAW DATA STRUCTURE (_data property if available): ═══════════════════════════════════════════════════════════════

Identification

KeyExampleMeaning
id.fromMetrueMessage originated from the current user.
id.remote[email protected]The chat JID the message belongs to (group address).
id.id3A82CCB4F9B25A74BFBAWhatsApp-generated unique message ID.
id.participant17313482965086@lidThe sender within that group (Linked-Device format).
id._serialized[email protected]_3A82CCB4F9B25A74BFBA_17313482965086@lidConcatenated unique key combining sender, chat, and message ID.

Delivery / Visibility

KeyExampleMeaning
viewedfalseMessage hasn’t been viewed locally.
ack1Delivery state (0 = queued β†’ 4 = played).
invisfalseHidden/system message flag.
isNewMsgtrueTreated as a new inbound/outbound message.
recvFreshtrueArrived recently from server.
viewModeVISIBLEDisplay state for the UI.

Content

KeyExampleMeaning
bodyTEST - Group messageActual text payload.
typechatMessage type (chat, image, video, etc.).
notifyNameGonzalo Munill GarridoDisplay name of the sender.
hasReactionfalseWhether reactions exist.
links[]Detected URLs.

Timestamps

KeyExampleMeaning
t1762705578Server-side message timestamp (s).
clientReceivedTsMillis1762705577584Client-side receive time (ms).
lastUpdateFromServerTs0Last server update tick (if any).

Participants

KeyExampleMeaning
from17313482965086@lidSender’s JID.
to[email protected]Receiver’s JID (group).
author17313482965086@lidMessage author inside the group.
mentionedJidList[]Individual mentions.
groupMentions[]Group-wide mentions such as @everyone.

Flags / State

KeyExampleMeaning
starfalseStarred by user.
kicNotifiedfalseβ€œKeep in Chat” notification handled.
isFromTemplatefalseSent via message template (business feature).
isAdsMediafalseMedia originated from an advertisement.
pollInvalidatedfalsePoll data invalid.
isSentCagPollCreationfalseClient-aggregated poll creation flag.
isVcardOverMmsDocumentfalsevCard file sent as MMS.
isForwardedfalseForwarded status.
isQuestionfalseInteractive question flag.
questionResponsesCount0Total responses if question.
readQuestionResponsesCount0Responses read by user.
isEventCanceled / eventInvalidatedfalseGroup event state flags.

Security

KeyExampleMeaning
messageSecret32-byte mapByte array used for integrity in E2E encryption metadata (not the key itself).
hostedBizEncStateMismatchfalseBusiness-account encryption consistency flag.

Media / Interaction

KeyExampleMeaning
productHeaderImageRejectedfalseBusiness product image validation.
lastPlaybackProgress0For audio/video progress.
isDynamicReplyButtonsMsgfalseHas interactive reply buttons.
isCarouselCardfalseBusiness carousel layout flag.
stickerSentTs0Sticker-send timestamp.
isAvatarfalseAvatar sticker indicator.

Call / Bot / Business Extras

KeyExampleMeaning
isVideoCallfalseMessage represents a video-call record.
callDurationnullDuration if call message.
invokedBotWidnullID of invoked chatbot.
bizBotTypenullType of business bot.
requiresDirectConnectionnullTrue if direct peer link required (e.g., call).
senderOrRecipientAccountTypeHostedfalseEither side uses hosted business account.

Thread / History / Placeholder

KeyExampleMeaning
latestEditMsgKeynullReference to edited version.
parentMsgIdnullParent message (reply/thread).
groupHistoryBundleMessageKeynullIdentifier for synced history bundle.
placeholderCreatedWhenAccountIsHostedfalsePlaceholder for hosted account.

Annex

Architecture Diagram: How Wireshark Captures Docker Traffic

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ RASPBERRY PI HOST                                                   β”‚
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚ tshark (running on HOST)                                   β”‚     β”‚
β”‚  β”‚ - Captures ALL network traffic on Pi                       β”‚     β”‚
β”‚  β”‚ - Including traffic from Docker containers                 β”‚     β”‚
β”‚  β”‚ - Saves to: <your-bridge-dir>/wireshark/capture.pcapng     β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                            β”‚                                        β”‚
β”‚                            β”‚ Captures packets                       β”‚
β”‚                            β–Ό                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚
β”‚  β”‚ Network Interface (eth0/wlan0)                           β”‚       β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚       β”‚
β”‚  β”‚ β”‚ HTTPS Traffic (TLS encrypted)                        β”‚ β”‚       β”‚
β”‚  β”‚ β”‚ From/To: web.whatsapp.com, *.whatsapp.net            β”‚ β”‚       β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚       β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚
β”‚                            β”‚                                        β”‚
β”‚                            β”‚                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚ DOCKER CONTAINER (telegram-whatsapp-bridge)                β”‚     β”‚
β”‚  β”‚                                                            β”‚     β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚     β”‚
β”‚  β”‚  β”‚ Node.js Process (runs as user "node", UID 1000)     β”‚   β”‚     β”‚
β”‚  β”‚  β”‚   β”œβ”€ whatsapp-web.js                                β”‚   β”‚     β”‚
β”‚  β”‚  β”‚   └─ Puppeteer                                      β”‚   β”‚     β”‚
β”‚  β”‚  β”‚      └─ Chromium (headless browser)                 β”‚   β”‚     β”‚
β”‚  β”‚  β”‚         └─ WhatsApp Web (web.whatsapp.com)          β”‚   β”‚     β”‚
β”‚  β”‚  β”‚            β”‚                                        β”‚   β”‚     β”‚
β”‚  β”‚  β”‚            β”‚ Makes HTTPS connections                β”‚   β”‚     β”‚
β”‚  β”‚  β”‚            β”‚ ENV: SSLKEYLOGFILE=/app/wireshark/...  β”‚   β”‚     β”‚
β”‚  β”‚  β”‚            β”‚                                        β”‚   β”‚     β”‚
β”‚  β”‚  β”‚            └─ Writes SSL keys to /app/wireshark/    β”‚   β”‚     β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚     β”‚
β”‚  β”‚                            β”‚                               β”‚     β”‚
β”‚  β”‚                            β”‚ (Volume Mount)                β”‚     β”‚
β”‚  β”‚                            β–Ό                               β”‚     β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚     β”‚
β”‚  β”‚  β”‚ /app/wireshark/ (inside container)                    β”‚ β”‚     β”‚
β”‚  β”‚  β”‚   β”œβ”€ sslkeys.log   ◄────────────────┐                 β”‚ β”‚     β”‚
β”‚  β”‚  β”‚   └─ capture.pcapng                 β”‚                 β”‚ β”‚     β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                           β”‚                         β”‚
β”‚       (Docker volume mount makes these    β”‚                         β”‚
β”‚        appear in both locations)          β”‚                         β”‚
β”‚                                           β”‚                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ <your-bridge-dir>/wireshark/ (on host) β”‚                      β”‚  β”‚
β”‚  β”‚   β”œβ”€ sslkeys.log   β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (Written by Chromium)β”‚  β”‚
β”‚  β”‚   └─ capture.pcapng  ◄────────────────── (Written by tshark)  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

                              β”‚
                              β”‚ Analysis happens on Mac
                              β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  YOUR MAC           β”‚
                    β”‚                     β”‚
                    β”‚  1. scp files       β”‚
                    β”‚  2. Open Wireshark  β”‚
                    β”‚  3. Load SSL keys   β”‚
                    β”‚  4. Decrypt TLS     β”‚
                    β”‚  5. Analyze packets β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

GΓ–D'S GATE β€” Buy on Amazon