Skip to content

Brazilian number rewrite in JID middleware breaks delivery to 9-digit mobiles #214

Open

Description

@joaomachado-pg

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/contact uses ValidateContactFields(), which always normalizes — even though ContactStruct exposes a formatJid field. 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, /edit use ValidateNumberField() and expose no formatJid at 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions