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
| Command | Payload | Window | The person sees |
|---|---|---|---|
message.edit | { text } | 15 minutes from the send | The new text, marked Edited |
message.unsend | none | 2 minutes from the send | The 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 asevent: moderation, told apart bytype. - 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.