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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingquestionFurther information is requested

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions