waiaconnect

Documentación / Completo

Mensajes editados y borrados

Markdown

¿Programás con IA? Conectala a tu cuenta por MCP: revisa tu configuración, crea webhooks y plantillas con tu confirmación y te escribe el código. Ver cómo →

Cuando un contacto edita un mensaje (por ejemplo, para corregir un importe), te llega message.edited; cuando lo borra, message.revoked. Los dos traen originalMessageId: el wamid del mensaje original, el mismo data.message.id que te llegó en su message.received. Con eso actualizás la fila que ya guardaste.

Lo que llega depende del tipo de número (lo decide Meta):

Número en convivencia (app + API). Edición → message.edited con textAvailable: true y data.message = la versión final (texto, o foto/video con el pie nuevo), con la misma forma que message.received. Borrado → message.revoked.

Número sólo Cloud API. Edición → message.edited con textAvailable: false, message: null y reason: "META_EDIT_TEXT_NOT_PROVIDED": Meta avisa que hubo una edición pero no manda el texto nuevo. Borrado → Meta no lo avisa.

WhatsApp deja editar sólo dentro de los 15 minutos de mandado el mensaje (y borrar, hasta 2 días). Una edición sin texto es una limitación de Meta: no hay forma de pedir el texto después.

El texto anterior no lo guardamos (WAIA Connect no guarda el contenido de los mensajes): si lo necesitás, está en tu base, bajo 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

Un webhook creado antes de estos eventos con «todos los eventos» no los recibe solo: volvé a elegir «todos» (PATCH con events: null, o el botón de su fila en el panel) o sumalos a la lista. newEventsAvailable dice cuáles faltan.

Un endpoint que recibe message.edited/message.revoked ya no recibe esa edición o ese borrado crudo como message.received (type: "edit", "revoke" o "unsupported"). Uno que no los recibe sigue igual que antes.

originalConnectMessageId es nuestro msg_… cuando el original lo mandaste por la API; para un mensaje del contacto viene null.

Deduplicá como cualquier evento: por el id del sobre (evt_…).