vercel / vercel/chat

WhatsApp: messages from username users are dropped: Meta sends from/wa_id as empty strings, not omitted

Open
#925 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.4k
Forks
314
Avg merge
1d 18h
Merged PRs (30d)
63

Description

Summary

@chat-adapter/whatsapp 4.40.0 drops inbound messages from WhatsApp users who have adopted a username, when Meta may not share their phone number. Meta sends the phone fields as empty strings, not omitted, and the adapter's identity resolution keeps the empty string.

Meta's own example

From Meta's Business-scoped user IDs page, "Example incoming text message from a user who has enabled the username feature" (business not messaged within 30 days, not in the user's contacts):

"contacts": [{ "profile": { "name": "Sheena Nelson", "username": "@realsheenanelson", "country_code": "US" }, "wa_id": "", "user_id": "user.93737..." }],
"messages": [{ "from": "", "from_user_id": "user.93737...", "id": "wamid...", "type": "text", ... }]

The same page describes from as "New value (empty). Will be empty if the user has enabled the username feature and you have not messaged the user's phone number in the last 30 days, or your business phone number is not in the user's WhatsApp contacts list." It uses empty strings for smb_message_echoes (to), history, status and calls webhooks too.

What happens

In fields():

const phone = inbound.system?.wa_id ?? inbound.from ?? contact?.wa_id; // "" is kept by ??
const bsuid = inbound.system?.user_id ?? inbound.from_user_id ?? contact?.user_id;
const userId = phone ?? bsuid ?? parent; // "" again
return userId ? { bsuid, parent, phone, userId } : null; // → null

resolve() then returns null, the webhook loop logs WhatsApp message has no user identifier and skips the message. resolve() has the same shape for system messages (const source = changed ? inbound.from : void 0).

Why the tests don't catch it

#794 assumed these fields "may be omitted (not empty)", and the BSUID tests added in #818 either include a phone number or leave from out (inbound({ from_user_id: "US.NEW" })). None sends the empty string Meta documents. The same code is on main today.

Suggested fix

Treat Meta's empty string as absent, e.g. || instead of ?? in fields() (and in resolve()'s source), plus a test built from Meta's example payload. We're carrying exactly that as a local pnpm patch for now; a payload with from: "", wa_id: "" and from_user_id then reaches the handler and the reply goes out by recipient.

Possibly related (lower confidence)

Meta's examples show BSUIDs as both US.13491208655302741918 and user.9373795779eb.... BSUID_PATTERN (/^[A-Z]{2}\.(?:ENT\.)?[A-Za-z0-9]{1,128}$/) matches only the first form, which matters in recipient()'s fallback when no route is stored. Worth confirming which formats Meta actually emits.

Version: @chat-adapter/whatsapp@4.40.0, chat@4.40.0.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in the WhatsApp adapter's fields() and resolve() entry points, then compare their empty-string handling with the BSUID tests from #818. Add a regression case based on Meta's payload with empty from and wa_id plus from_user_id, and verify the message reaches the handler and the recipient fallback can send a reply.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.