Short summary
Only the first update of a webhook POST is dispatched. When Meta batches several statuses into one payload, the rest are dropped with no error and no log above DEBUG.
Steps to reproduce
from_update reads fixed indexes at three levels:
# pywa/types/message_status.py:158
status = (value := (entry := update["entry"][0])["changes"][0]["value"])["statuses"][status_idx]
entry[0], changes[0] and status_idx=0. The same shape appears in message.py:203, callback.py:293 and other files.
Measured on 4.4.0, using this repo's own fixture tests/data/updates/message_status.json (sent) as the base:
import copy, json
import httpx
from pywa import WhatsApp
REAL = json.load(open("tests/data/updates/message_status.json"))["sent"]
seen = []
wa = WhatsApp(phone_id="1122334455667", token="t", server=None, app_secret="s", verify_token="vt",
session=httpx.Client(transport=httpx.MockTransport(lambda r: httpx.Response(200, json={}))))
@wa.on_message_status
def on_status(_c, s):
seen.append(s.id)
def run(label, payload):
seen.clear()
wa.webhook_update_handler(update=json.dumps(payload).encode())
print(f"{label:<34} -> {len(seen)} dispatched: {seen}")
run("1 status (baseline)", REAL)
p = copy.deepcopy(REAL)
sts = p["entry"][0]["changes"][0]["value"]["statuses"]
for i in (2, 3):
s = copy.deepcopy(sts[0]); s["id"] = f"wamid.{i}"; sts.append(s)
run("3 statuses in one change", p)
p = copy.deepcopy(REAL)
c2 = copy.deepcopy(p["entry"][0]["changes"][0])
c2["value"]["metadata"]["phone_number_id"] = "9999999999999"
c2["value"]["statuses"][0]["id"] = "wamid.SECOND_NUMBER"
p["entry"][0]["changes"].append(c2)
run("2 changes, different numbers", p)
p = copy.deepcopy(REAL)
e2 = copy.deepcopy(p["entry"][0])
e2["changes"][0]["value"]["statuses"][0]["id"] = "wamid.ENTRY2"
p["entry"].append(e2)
run("2 entries", p)
Output:
1 status (baseline) -> 1 dispatched: ['wamid.xyzxyz']
3 statuses in one change -> 1 dispatched: ['wamid.xyzxyz']
2 changes, different numbers -> 1 dispatched: ['wamid.xyzxyz']
2 entries -> 1 dispatched: ['wamid.xyzxyz']
Expected behavior
Every status in the payload dispatched once, each with its own metadata.phone_number_id.
The batching matters in two situations:
- Volume. Meta groups statuses into a single POST, so delivery events are lost exactly when there is traffic.
- A WABA with more than one phone number. A payload can mix
changes from different numbers, and today the single dispatched update decides the number for the whole POST.
If dropping is ever intentional, a WARNING when len(statuses) > 1 or len(changes) > 1 would at least make it visible — right now the only clue is that a status never arrives.
Environment
pywa 4.4.0, Python 3.13.
Happy to send a PR if you want it — iterating entries × changes × statuses and constructing one update per status, with contact_idx following status_idx.
Short summary
Only the first update of a webhook POST is dispatched. When Meta batches several statuses into one payload, the rest are dropped with no error and no log above DEBUG.
Steps to reproduce
from_updatereads fixed indexes at three levels:entry[0],changes[0]andstatus_idx=0. The same shape appears inmessage.py:203,callback.py:293and other files.Measured on 4.4.0, using this repo's own fixture
tests/data/updates/message_status.json(sent) as the base:Output:
Expected behavior
Every status in the payload dispatched once, each with its own
metadata.phone_number_id.The batching matters in two situations:
changesfrom different numbers, and today the single dispatched update decides the number for the whole POST.If dropping is ever intentional, a WARNING when
len(statuses) > 1orlen(changes) > 1would at least make it visible — right now the only clue is that a status never arrives.Environment
pywa 4.4.0, Python 3.13.
Happy to send a PR if you want it — iterating entries × changes × statuses and constructing one update per status, with
contact_idxfollowingstatus_idx.