waiaconnect

Documentation / Complete

Edited and deleted messages

Markdown

Building with AI? Connect it to your account over MCP: it checks your setup, creates webhooks and templates with your confirmation and writes the code for you. See how →

When a contact edits a message (for example, to fix an amount), you get message.edited; when they delete it, message.revoked. Both carry originalMessageId: the wamid of the original message, the same data.message.id you got in its message.received. Use it to update the row you already stored.

What arrives depends on the kind of number (Meta decides):

Coexistence number (app + API). Edit → message.edited with textAvailable: true and data.message = the final version (text, or photo/video with the new caption), shaped like message.received. Delete → message.revoked.

Cloud API-only number. Edit → message.edited with textAvailable: false, message: null and reason: "META_EDIT_TEXT_NOT_PROVIDED": Meta reports the edit but does not send the new text. Delete → Meta does not report it.

WhatsApp only allows editing within 15 minutes of sending (and deleting, up to 2 days). An edit without text is a Meta limitation: there is no way to fetch the text later.

We don't keep the previous text (WAIA Connect does not store message content): if you need it, it is in your database, under originalMessageId.

# Nothing to call: WAIA Connect POSTs message.edited / message.revoked to your webhook.
# Read a saved body: the final text (or null when Meta did not send it) and the original id.
jq -r 'if .type == "message.edited"
  then "\(.data.originalMessageId) edited -> " + (if .data.textAvailable then (.data.message.text.body // .data.message.image.caption // .data.message.video.caption // "") else "(text not provided by Meta)" end)
  else "\(.data.originalMessageId) deleted" end' body.json
// Inside your webhook handler, after verifying the signature.
const evt = req.body;
if (evt.type === "message.edited") {
  const d = evt.data;
  if (d.textAvailable) {
    const m = d.message; // same shape as message.received → data.message
    const text = m.text?.body ?? m.image?.caption ?? m.video?.caption ?? null;
    await db.messages.update({ wamid: d.originalMessageId }, { text, editedAt: d.editedAt }); // your code
  } else {
    // Cloud API-only number: Meta says it was edited but sends no text (reason META_EDIT_TEXT_NOT_PROVIDED)
    if (d.originalMessageId) await db.messages.update({ wamid: d.originalMessageId }, { editedAt: d.editedAt, textOutdated: true });
  }
} else if (evt.type === "message.revoked") {
  await db.messages.update({ wamid: evt.data.originalMessageId }, { deletedAt: evt.data.revokedAt }); // your code
}
<?php
// Inside your webhook handler, after verifying the signature.
$evt = json_decode(file_get_contents("php://input"), true);
$d = $evt["data"] ?? [];
if ($evt["type"] === "message.edited") {
  if ($d["textAvailable"]) {
    $m = $d["message"]; // same shape as message.received → data.message
    $text = $m["text"]["body"] ?? $m["image"]["caption"] ?? $m["video"]["caption"] ?? null;
    update_message($d["originalMessageId"], ["text" => $text, "edited_at" => $d["editedAt"]]); // your code
  } elseif ($d["originalMessageId"]) {
    // Cloud API-only number: Meta sends no text (reason META_EDIT_TEXT_NOT_PROVIDED)
    update_message($d["originalMessageId"], ["edited_at" => $d["editedAt"], "text_outdated" => true]);
  }
} elseif ($evt["type"] === "message.revoked") {
  update_message($d["originalMessageId"], ["deleted_at" => $d["revokedAt"]]); // your code
}
# Inside your webhook handler, after verifying the signature.
evt = request.get_json()
d = evt.get("data", {})
if evt["type"] == "message.edited":
    if d["textAvailable"]:
        m = d["message"]  # same shape as message.received -> data.message
        text = (m.get("text") or {}).get("body") or (m.get("image") or {}).get("caption") or (m.get("video") or {}).get("caption")
        update_message(d["originalMessageId"], text=text, edited_at=d["editedAt"])  # your code
    elif d["originalMessageId"]:
        # Cloud API-only number: Meta sends no text (reason META_EDIT_TEXT_NOT_PROVIDED)
        update_message(d["originalMessageId"], edited_at=d["editedAt"], text_outdated=True)
elif evt["type"] == "message.revoked":
    update_message(d["originalMessageId"], deleted_at=d["revokedAt"])  # your code

A webhook created before these events with "all events" does not get them on its own: choose "all" again (PATCH with events: null, or the button on its row in the dashboard) or add them to the list. newEventsAvailable says which are missing.

An endpoint that receives message.edited/message.revoked no longer receives that edit or delete raw as message.received (type: "edit", "revoke" or "unsupported"). One that doesn't receive them stays as before.

originalConnectMessageId is our msg_… when you sent the original through the API; for a contact's message it is null.

Deduplicate like any event: by the envelope id (evt_…).