# Edited and deleted messages

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

## Examples

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

## Notes

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