# Mensajes editados y borrados

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`.

## Ejemplos

### cURL

```bash
# 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
```

### Node.js

```javascript
// 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

```php
<?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
}
```

### Python

```python
# 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
```

## Notas

- **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_…`).
