Messages

Edits and unsends

Change or take back a message the line sent, and hear when a contact edits or unsends one of theirs.

On an iMessage line an edit or an unsend goes both ways. Your line can change or take back a message it sent, with two commands. A contact can do the same to a message they sent, and you hear about it through two events you opt into.

In neither direction is the message this platform stores rewritten: its text stays what was first sent, and an unsent message stays in the thread.

Editing and unsending your own

CommandPayloadWindowThe person sees
message.edit{ text }15 minutes from the sendThe new text, marked Edited
message.unsendnone2 minutes from the sendThe message gone, marked Unsent

Both target the message by our id: { conversationId, messageId }, where messageId is the one the send's command frame carried — never the transport's externalId. A group's conversation is allowed.

{
  "target": {
    "conversationId": "a0000000-0000-4000-8000-000000000001",
    "messageId": "0192a7c4-5a31-7bd2-8e6f-1d0c9b8a7f65"
  },
  "payload": { "text": "Your order shipped. It arrives Thursday." }
}

Each is refused with 422 validation_failed at target.messageId before anything is sent, its reason saying why:

  • invalid_target — the message is not in that conversation, or was not sent from this line;
  • not_yet_addressable — the transport has not given it an id: it is still being sent, it failed, or it is a rich link, which never gets one;
  • window_elapsed — it is past the verb's window.

Any key but an edit's text is a 422 at payload.<key>. While the line's sending is paused, both are refused at once with 503 upstream_unavailable, reason: outbound_paused, rather than held past the window.

How they settle

The 202 is followed by a command frame: succeeded once the line read the change back; failed with stale_target when the line no longer finds the message inside its window — which is also how an unsend that already worked settles when it is sent again — or with invalid_target.

moderation_unverified: re-read the thread, never retry blindly

A frame ambiguous with errorCode: "moderation_unverified" means the line could not prove what happened: the message may or may not have changed. Look at the thread on the phone before you do anything else to it, and do not wait for it to settle further — it may never do.

Hearing about a contact's

When a contact edits or unsends a message they sent to the line, two event types report it: message.edited and message.unsent. Neither arrives unless you ask for it.

  • On the stream, open it with ?events=moderation. Both arrive as event: moderation, told apart by type.
  • At a webhook endpoint, subscribe to them by name. They are not in a new endpoint's default set.
event: moderation
data: {"type":"message.edited","messageId":"0192a7c4-6c02-7d4e-9a13-5b8f2c1d0e44","conversationId":"a0000000-0000-4000-8000-000000000001","externalId":"guid-1","text":"See you at 7","previousText":"See you at 6","editedAt":"2026-09-30T18:10:04.000Z","simulated":false}

Each names the message by the messageId and externalId its message frame carried. An edit carries the new text, and previousText where the platform kept it; this is the only place the new text arrives. Only a message this platform filed is framed: an edit of one sent before the line was connected is not. Your line's own edits and unsends are command frames, never these. The fields are in the stream reference.

In test

The sandbox rehearses both directions. Your message.edit and message.unsend run on the relay's own executor against the sandbox phone and settle as a live line's do, after the same refusals. The phone can edit and unsend a message the contact sent through the console's sandbox routes — the Sandbox page has no control for it yet — and each reaches you with "simulated": true. The sandbox does not hold the phone to Apple's windows, or to its limit on how many times a message can be edited.

On this page