Repository navigation
Brazilian number rewrite in JID middleware breaks delivery to 9-digit mobiles #214
Description
Summary
The JID validation middleware rewrites Brazilian numbers before any verification, and the rewrite is irreversible. This makes /send/* fail for mobile numbers whose WhatsApp registration includes the ninth digit — even though sendService already contains the correct logic to handle both cases.
Reproduced on tag 0.7.2; the relevant code is identical on main.
The rule
formatBRNumber (pkg/utils/utils.go) drops the first digit after the area code:
if firstDigitNum >= 7 && dddNum >= 31 {
return jid[:4] + jid[5:]
}For DDD >= 31 this is applied unconditionally. It is correct for older mobile numbers from those regions (registered with 8 digits), but wrong for the newer 9-digit ranges, which only exist in full form.
Evidence
Using /user/check on the same number, which is the cleanest isolation since it exposes formatJid:
formatJid=true Query "+55469XXXXXXX" (digit dropped) IsInWhatsapp=false
formatJid=false Query "55469XXXXXXXX" (as sent) IsInWhatsapp=true,
JID 55469XXXXXXXX@s.whatsapp.net
With the default (formatJid unset → true), /send/text fails:
SendMessage called for number: +55469XXXXXXX@s.whatsapp.net
Error validating message fields or user check: number +55469XXXXXXX@s.whatsapp.net is not registered on WhatsApp
In our production data, 2,488 contacts have this shape. None of them could ever receive a message from the API until we started sending formatJid: false.
Why this is worth fixing upstream
validateAndCheckUserExists (pkg/sendMessage/service/send_service.go) already does the right thing: it calls checkSingleUserExists with formatJid=false, falls back to true if not found, and then messages the remoteJID that WhatsApp itself returned. That logic covers both the legacy 8-digit and the new 9-digit cases.
The problem is that it never sees the original number. ValidateNumberFieldWithFormatJid (pkg/middleware/jid_validation_middleware.go) rewrites the request body before the handler runs, and the dropped digit cannot be recovered — so the service's own fallback is checking two variants of an already-broken number.
Suggestion: let the middleware validate without mutating, and leave the formatting decision to validateAndCheckUserExists, which can verify against the server before committing to a JID.
Secondary issue: formatJid has no effect on some routes
In pkg/routes/routes.go, only /send/text|link|media|poll|sticker|location|button|list|carousel and /user/check use ValidateNumberFieldWithFormatJid. Notably:
/send/contactusesValidateContactFields(), which always normalizes — even thoughContactStructexposes aformatJidfield. The field is silently ignored, so sending a vCard to one of these numbers cannot be made to work./message/react,/markread,/markplayed,/presence,/delete,/edituseValidateNumberField()and expose noformatJidat all, so reacting to or marking as read a message from one of these contacts always fails.
Also worth noting: passing a full JID does not bypass the rewrite — validateMessageFields splits it back apart (strings.Split(phone, "@")[0]) and re-applies CreateJID.
Activity