Resolving a WhatsApp @lid to a real phone number
WhatsApp is retiring the phone number as an identifier. What a @lid is, what it breaks downstream, and how to model contacts so the next change is boring.
Your webhook used to hand you a phone number. Some of them now hand you 219478003311902@lid instead, and the contact lookup that has worked since the day you shipped returns nothing.
Nothing in your code broke. WhatsApp changed what a sender is.
The platform is part-way through separating a user's identity from their phone number, and the identifier you are looking at is the new one: an opaque account handle that is stable, routable, and deliberately tells you nothing about the number behind it. Every integrator is going to meet it, the only variable is whether you meet it in a staging environment or in a customer's support queue.
What a @lid actually is
A linked identity is WhatsApp's internal identifier for an account.
You already know its older sibling. The familiar form is a phone-number JID, [email protected], which is just the number with a server suffix bolted on. It is not really an identifier at all, it is a phone number wearing one.
A linked identity looks like 219478003311902@lid and is not derived from anything you can read. It identifies the account without disclosing the number, which is the entire point of it.
Both forms address the same person. On a given conversation WhatsApp picks one, and increasingly it picks the one that does not leak a phone number.
Why it turned up now
Because the phone number is being retired as the thing that identifies you.
On 30 June 2026 WhatsApp announced usernames, and began letting people reserve one. The stated direction is that people will be able to be found and contacted by username only, and not by number, with the wider rollout landing across the coming months.
The official API surface is moving in the same direction on its own schedule. Meta's business-scoped user ID started appearing in webhooks in early April 2026, is assigned to the user_id parameter on all message webhooks whether or not the user has enabled usernames, and takes the form of a two-letter country code, a full stop, and up to 128 alphanumeric characters, like US.13491208655302741918. Meta's documentation states that supporting it is required for all partners and directly-integrated businesses, and that its APIs would not accept sends addressed to one until July 2026.
Two different surfaces, two different identifier formats, one change underneath. Nobody gets to sit this out.
What actually breaks
Not the message flow. The message arrives, it is decrypted, it has a body and a timestamp and it reaches your webhook exactly as before.
What breaks is everything downstream that assumed a sender was a number.
Contact matching goes first. Your CRM keyed its records on E.164, so a lookup on 219478003311902 finds nobody, and depending on how forgiving your code is you either drop the message or you create a brand new contact for a customer you have been talking to for a year.
Then deduplication fails in the other direction. The same human now reaches you under 2 identifiers, one of them a number and one of them not, so your unique-customer count inflates, your conversation threading splits mid-thread, and your reporting quietly stops being true.
Then anything that writes back to a human-readable system produces garbage. Notification emails addressed to a 15-digit string. Support tickets with no phone number on them. An agent looking at a conversation with nothing to dial.
Group participants are where most teams notice it first, because a group can contain members addressed both ways at once.
The fixes that do not work
Stripping the suffix and treating the digits as a phone number. They are not a phone number, they are not a badly formatted phone number, and there is no country code hiding at the front. A lookup on them will either fail or, worse, match somebody else.
Guessing from context. Matching a linked identity to a contact by push name, by profile photo, or by which conversation it appeared in will be right often enough to feel like it worked and wrong often enough to merge 2 customers' histories. There is no undo for that in a CRM.
Ignoring the identifier and keying on the conversation instead. This survives until a customer messages you from a second surface, or a group thread needs attributing, and then you have a data model with no concept of a person in it.
Waiting for it to be reverted. It is not a bug, a beta or a regional experiment. It is a documented platform direction with dates attached.
What does work
Stop treating the phone number as the primary key. That sentence is the whole migration and the rest is consequence.
Give your contact model its own internal identifier, and hang every external identifier off it as an attribute: the phone number when you have one, the linked identity when you have one, the business-scoped ID on the official surface when you have that. A contact with 2 identifiers and 1 identity is correct. A contact per identifier is the bug you are trying to avoid.
Then build the mapping from the platform, not from inference. WhatsApp exposes the correspondence between the 2 forms, and inbound events frequently carry both, which is your cheapest and most reliable source: when a message arrives with a linked identity and a phone number alongside it, record the pair. Treat the mapping as cached rather than permanent, because the platform can change a linked identity and will tell you when it does.
Make the absence explicit. Every place in your product that renders a phone number needs a defined behaviour for not having one, decided deliberately rather than by whatever your template does with a null. That is a product decision and it will outlive this particular change.
And make a linked identity a first-class thing you can send to. If your outbound path can only accept an E.164 number, you cannot reply to a growing share of the people who message you, which is a more urgent problem than any reporting inaccuracy.
What you can actually do this week
Search your codebase for the places a sender is parsed into a phone number, and count them. In most integrations the number is between 6 and 20, and the exercise takes an afternoon.
Then check what your contact table's primary key is. If it is a phone number, that is the migration, and it is better done in a quiet week than during an incident.
WALayer hands you the identifiers the platform provides, including both forms when both are present on an event, so the mapping work happens against real data rather than a guess. The docs cover the event shapes and the developers page has the SDKs. If you are also chasing a linking problem rather than an identity one, why WhatsApp sessions drop is the other half of that afternoon.
One thing worth saying plainly: this class of change is not finished. The phone number has been the identifier for 15 years and unwinding that will take more than one release, so the design goal is not to handle @lid specifically. It is to stop being surprised the next time an identifier changes shape.