WhatsApp: messages from username users are dropped: Meta sends from/wa_id as empty strings, not omitted
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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