Description
What is the webhook contacts object?
The webhook contacts object is the contacts array that every inbound message on the messages webhook carries next to the messages array, describing the sender. It sits inside value, the part of the webhook payload your code reads. Each item has a profile object with the name the user set in WhatsApp, a wa_id (the user's WhatsApp ID, normally their phone number as digits without a leading plus) and, since BSUIDs began appearing in webhooks in early April 2026, a user_id: the business-scoped user ID, written as a country code, a dot and up to 128 alphanumeric characters. If you have enabled parent BSUIDs, a parent_user_id appears too.
WhatsApp usernames change what you can rely on. When a user has adopted a username, profile.username appears, and the phone number — wa_id in contacts and from in the message — is omitted unless you messaged or called that number, or it messaged or called you, in the last 30 days (checked per business phone number), or the user is in your contact book. user_id is always included, and the message carries the same value as from_user_id. Status webhooks follow similar rules: the contacts block leaves out username on sent statuses and is dropped entirely on failed ones.
Do not confuse it with the contacts message type. When a customer shares a contact card, that card is the content of a message (type: "contacts") inside messages[], with names, phone numbers and emails of someone else. The contacts object is metadata about the person who triggered the event. And profile.name is whatever the user typed into WhatsApp — a display hint, never verified identity.
Key takeaways
- The webhook contacts object is the contacts array beside messages or statuses that says who a WhatsApp event is about.
- Each item can carry profile.name, profile.username, wa_id and user_id, the business-scoped user ID (BSUID).
- user_id is always present; wa_id is omitted for username users unless you were in touch within 30 days or they are in your contact book.
- profile.name is whatever the user typed into WhatsApp — use it for display, never to identify or merge customers.
- It is not the contacts message type, in which a customer shares someone else's contact card inside messages[].
Properties
The webhook contacts object: key facts and limits
- Location
- entry[].changes[].value.contacts[]
- Always present
- user_id (the BSUID)
- Sometimes omitted
- wa_id, when a username hides the number
- Phone shown if
- Contact within 30 days, or in your contact book
- BSUID format
- Country code, dot, up to 128 alphanumerics
- BSUID scope
- One business portfolio
- In webhooks since
- Early April 2026
- BSUID change
- System message on messages; no user_id_update field
Mechanics
How does the webhook contacts object work?
step 1 — Find it next to the event
Read
entry[].changes[].value.contacts[]alongsidemessages[](orstatuses[]). It is part of the samevalue, so process them together.step 2 — Match contact to message
Pair
contacts[].wa_idwithmessages[].fromwhen the phone is present, orcontacts[].user_idwithmessages[].from_user_idwhen it is not. Match explicitly rather than assuming item 0.step 3 — Resolve the customer
Look up by
user_idfirst, then bywa_id. Create the record if neither matches, and store every identifier you received so later events can match on either.step 4 — Refresh the display name
Update
profile.nameandprofile.usernameas display fields. Never merge two customers or grant access because their names match.step 5 — Follow identifier changes
A BSUID is regenerated when a user changes their phone number. There is no
user_id_updatefield to subscribe to: the change arrives onmessagesas a system message (user_changed_number, oruser_changed_user_idwhen no number can be shared), so map the old ID to the new one when you see it.
Usage
The webhook contacts object in code
"contacts": [{"profile": { "name": "Arjun", "username": "arjunreads" },"user_id": "IN.40731866125599102847"}],"messages": [{"from_user_id": "IN.40731866125599102847","id": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAEhggNEFCRDlFQkM","timestamp": "1792390400","type": "text","text": { "body": "Is the new Bengali poetry anthology in stock?" }}]
No wa_id and no from: the user_id is the only stable key in this event.
Caveats
Common mistakes with the webhook contacts object
Assuming wa_id is always there
Code such as contacts[0].wa_id.startsWith("91") throws on username users. Treat wa_id as optional and fall back to user_id.
Using profile.name as identity
Users choose and change their profile name, and many share common names. Use it for greeting and display, never to merge records or verify who someone is.
Reading contacts[0] blindly
Match the contact to the message by wa_id or user_id. It costs one line and stays correct if a payload ever carries more than one item.
Treating wa_id as an E.164 number
wa_id has no plus sign, and Meta notes it may not match the number you have on file. Normalise both sides to the E.164 phone number format before comparing, and store what WhatsApp sends as its own field.
Questions
Webhook Contacts Object FAQs
- Why is wa_id missing from my WhatsApp webhook?
- Because the user has adopted a username and your business number has not been in touch with them recently. The phone appears only after a message or call either way within 30 days, or if the user is in your contact book; user_id is always there.
- Is wa_id the customer's phone number?
- Usually, but not always. It is normally the number in digits without the plus sign, yet Meta notes it may not match the number you hold, and it is omitted for some username users. Treat it as WhatsApp's ID for the person.
- Can I use a BSUID across my businesses?
- Only inside one business portfolio. The same person has a different BSUID under another portfolio, and a new one if they change their phone number.
- Can I send a message to a user_id?
- Meta is adding the ability to send by BSUID, so check what your integration and provider support today. Until BSUID sends work end to end for you, keep the last known phone number alongside the BSUID and send to the number.
Why it matters
Why does the webhook contacts object matter?
The contacts object is how every event finds its customer record. CRMs, inboxes and bots that key everything on the phone number worked for years because wa_id was always present; with usernames rolling out through 2026, some events will arrive with only a user_id. An integration that stores both identifiers now, and looks people up by either, keeps threads joined up. One that assumes wa_id will create duplicate contacts, misroute replies and lose conversation history the moment a customer hides their number.
Example
Example: A Kolkata bookshop's inbox after usernames
A Kolkata bookshop's inbox has always keyed customers on phone number. A long-time customer adopts a username and, two months after their last chat, asks whether a new novel is in stock. The webhook arrives with profile.name, profile.username and user_id, but no wa_id or from, because nobody has messaged that number in 30 days. Because the shop backfilled user_id into its customer table when BSUIDs first appeared, the lookup succeeds, the agent sees the customer's order history, and the reply goes into the existing thread instead of a new, anonymous one.
Notes
Best practices for the webhook contacts object
- Backfill user_id onto existing customers from incoming webhooks now, before username adoption grows.
- Index customers on both BSUID and wa_id so either lookup is fast.
- Mask wa_id in logs; it is personal data under the DPDP Act.