0.2 Reading the new payload

An inbound message can carry a phone number, a BSUID and a username. Which of them actually arrive depends on the user and on how far the rollout has reached them.

7 min

The migration has two phases and the payload shows both. Meta spent 2026 adding identifiers alongside the phone number, and has now started removing the phone number for users who adopt a username. Here's the same inbound message at all three points.

Phase zero: what you built against

If your integration predates April 2026, this is the shape it expects:

{  "contacts": [    {      "profile": { "name": "Sheena Nelson" },      "wa_id": "16505551234"    }  ],  "messages": [    {      "from": "16505551234",      "id": "wamid.HBgLMTY1MDM4Nzk0MzkV...",      "type": "text",      "text": { "body": "Does it come in another color?" }    }  ]}

One identifier, in two places. Lookups, CRM joins and dedupe rules all key on that phone number, because nothing else was available.

Phase one: Meta adds the new identifiers

Since April 2026, the same message arrives like this. Nothing was taken away:

{  "contacts": [    {      "profile": { "name": "Sheena Nelson" },      "wa_id": "16505551234",      "user_id": "US.13491208655302741918",      "parent_user_id": "US.ENT.11815799212886844830"    }  ],  "messages": [    {      "from": "16505551234",      "from_user_id": "US.13491208655302741918",      "from_parent_user_id": "US.ENT.11815799212886844830",      "id": "wamid.HBgLMTY1MDM4Nzk0MzkV...",      "type": "text",      "text": { "body": "Does it come in another color?" }    }  ]}

This is the payload almost every integration is receiving right now. The four new fields sit alongside the phone number, which is unchanged. No error, no deprecation warning, nothing that required a response. Most integrations don't read them.

The two parent_ fields only show up if you enrol in parent BSUIDs, which Chapter 1.1 covers. The other two arrive for everyone.

Phase two: Meta removes the phone number

Now the same person adopts a username and falls outside the 30-day window:

{  "contacts": [    {      "profile": { "name": "Sheena Nelson" },      "profile": { "name": "Sheena Nelson", "username": "realsheenanelson" },      "wa_id": "16505551234",      "user_id": "US.13491208655302741918",      "parent_user_id": "US.ENT.11815799212886844830"    }  ],  "messages": [    {      "from": "16505551234",      "from_user_id": "US.13491208655302741918",      "from_parent_user_id": "US.ENT.11815799212886844830",      "id": "wamid.HBgLMTY1MDM4Nzk0MzkV...",      "type": "text",      "text": { "body": "Does it come in another color?" }    }  ]}

wa_id and from are removed: not empty strings, not nulls, just absent from the payload. Any code that reads message["from"] and assumes a value gets nothing. The four identifiers added in phase one are untouched, in the same places under the same keys.

The four identifiers

FieldWhat it isAddedWhen it appears
user_idThe BSUIDApril 2026Always
parent_user_idParent BSUIDApril 2026Only for enrolled multi-portfolio businesses
usernameThe user's chosen usernameWith the username rolloutOnly if they have adopted one
wa_id / fromPhone numberAlways existedOnly under the conditions below

Three of the four are new. username tells you this customer's phone number could stop arriving, and a missing wa_id tells you it didn't arrive this time. user_id is the only one you can count on.

When you still get a phone number

Meta doesn't cut you off immediately. It still sends the phone number if any of these hold:

  • You messaged or called that phone number in the last 30 days.
  • You received a message or call from it in the last 30 days.
  • The user is in your contact book, which we cover in Chapter 2.2.

The trap in the 30-day rule

Meta evaluates the 30-day window per business phone number, not per portfolio.

If a customer talks to your support number regularly but has never interacted with your notifications number, webhooks for the notifications number will arrive with no phone number at all, even though another number in the same portfolio talks to them every week.

Outbound status webhooks

Delivery statuses went through the same two phases, under different field names. An entire contacts block appeared on sent, delivered and read statuses, and the statuses block gained its own identifiers:

{  "contacts": [    {      "profile": { "name": "Sheena Nelson", "username": "realsheenanelson" },      "wa_id": "16505551234",      "user_id": "US.13491208655302741918"    }  ],  "statuses": [    {      "id": "wamid.HBgLMTY1MDM4Nzk0MzkV...",      "status": "delivered",      "recipient_id": "16505551234",      "recipient_user_id": "US.13491208655302741918"    }  ]}

recipient_user_id is always present. recipient_id holds the phone number and can be omitted, and on failed statuses Meta drops the contacts block entirely.

If you reconcile delivery receipts by phone number, that breaks too, for the same reason your inbound handler does.

How these reach you

Everything above is Meta's payload. If you integrate through Kapso you have two ways to receive it, and the choice changes what you see.

Kapso webhooksMeta webhooks
PayloadKapso's own format, normalisedMeta's payload, forwarded as-is
Identifiersbusiness_scoped_user_id, parent_business_scoped_user_id and username, on both the contact and the messageThe Meta field names shown above
ScopeSubscribe to the events you wantEverything for that phone number
BufferingAvailableNot available

The identifiers are in both. You don't need raw Meta forwarding to get BSUIDs, and on Kapso webhooks you don't have to unpack entry[].changes[].value to find them.