Skip to content

Bug: only the first update of a batched webhook POST is dispatched #229

Description

@geanportugal

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions