Tapbacks
Tapbacks are iMessage's inline reactions — the heart, thumbs-up, "haha", eyes, and friends that attach to a specific message bubble. Agents can send them, humans send them back, and message reads always carry the current live set.
Tapbacks follow Apple's semantics: one live tapback per sender per message. Sending a new tapback to a message you already reacted to replaces your previous one, and your agent can take its own tapback back entirely. When the human swaps or removes theirs, message reads and webhooks reflect it.
Send tapback POST
React to a message a human sent in one of your agent's 1:1 or ready group conversations. iMessage has no concept of reacting to your own messages, so an agent can only tapback an inbound message — targeting one of the agent's own messages returns 422 in either conversation type.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
message_id | UUID | Yes | The message being reacted to |
reaction | string | Yes | One of "love", "like", "dislike", "laugh", "emphasize", "question", or "eyes". "eyes" displays as 👀. Arbitrary emoji and "custom" are not valid outbound values |
part_index | integer | No | Part of a multi-part message to react to. Defaults to 0 |
Request example
Response (201)
If the agent already had a live tapback on that message part, it is replaced — exactly what the human sees happen on their device.
For a group reaction, assignment_id is null and remote_number identifies the participant who sent the target message. The request and the rest of the response use the same shape as 1:1.
Error responses
| Status | Description |
|---|---|
| 400 | The conversation's identity is not enabled for iMessage, or the target message cannot accept reactions yet |
| 403 | A current recipient is blocked by a contact rule or has opted out (recipient_opted_out) |
| 404 | Message not found, or not visible to the caller |
| 409 | The recipient disconnected, group_conversation_not_ready because the group is not ready on its current dedicated line, or current group membership cannot be resolved consistently |
| 422 | Unknown reaction value (including an arbitrary emoji or "custom"), invalid part_index, or the target is the agent's own message (only the human's messages can be reacted to) |
| 502 | Upstream delivery failure — safe to retry. The exception is reaction_delivery_unconfirmed (committed: "unknown"), where the tapback may already have landed: read the message before sending the same value again, since a repeat can toggle it back off |
| 503 | group_membership_unavailable because current membership could not be verified; retry with backoff |
| 504 | Reaction could not be sent before the request deadline; delivery did not begin, so it is safe to retry |
Remove tapback DELETE
Take back a tapback your agent sent. Pass the reaction id — the one returned when you sent it, or the one on the entry in a message's reactions array. The tapback disappears from the human's message bubble, and it drops out of the reactions array on the next read.
Only your agent's own tapbacks can be removed. A human's tapback belongs to them; targeting one returns 422.
Response (204)
No body. Removing a tapback Inkbox already shows as gone — because you removed it, or because a newer tapback replaced it — also returns 204, without sending anything to the recipient.
Error responses
| Status | Description |
|---|---|
| 400 | The conversation's identity is not enabled for iMessage |
| 403 | The recipient is blocked by a contact rule or has opted out (recipient_opted_out) |
| 404 | Reaction not found, or not visible to the caller |
| 409 | reaction_dispatch_in_progress because a tapback on this message part is still being delivered — including this one, when it was queued and has not settled — the recipient disconnected, group_conversation_not_ready, or group_membership_inconsistent |
| 422 | The tapback was sent by the human, not by your agent |
| 502 | reaction_removal_rejected, or reaction_removal_unconfirmed when the outcome is unknown — safe to retry either |
| 503 | group_membership_unavailable because current group membership could not be verified; retry with backoff |
| 504 | The removal could not be sent before the request deadline; retry |
Every failure leaves the tapback in place rather than clearing it locally, so retrying is the right response to an error — including the unconfirmed one, where the removal may already have reached the recipient. In that case your next read still shows the tapback live until a retry succeeds.
Example
Receiving tapbacks
Tapbacks from humans in 1:1 and group conversations arrive two ways:
- On message reads — every message object carries a
reactionsarray with the live tapbacks targeting it, oldest first. - As webhooks —
imessage.reaction_receivedfires when a human tapbacks one of your agent's messages.
Humans can react with any emoji, not just the classic six. Those arrive with reaction: "custom" and the literal emoji in custom_emoji:
Arbitrary custom-emoji tapbacks are receive-only. Outbound sends accept the classic six plus "eyes"; pass the named "eyes" value rather than the literal 👀 emoji.
When a human removes a tapback, it simply disappears from the message's reactions array on the next read. Removals do not fire a webhook.
Group tapbacks use the same target-message semantics: every reaction attaches to one target_message_id and part_index, a new reaction from the same sender replaces their previous reaction on that message part, and a removal clears it. On group reactions, assignment_id is null and remote_number identifies the participant associated with the reaction.
Reaction object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Reaction ID |
conversation_id | string (UUID) | The conversation containing the target message |
assignment_id | string (UUID) | null | The 1:1 connection carrying the conversation; null for group reactions |
target_message_id | string (UUID) | The message this tapback targets |
direction | string | "inbound" (from the human) or "outbound" (from the agent) |
reaction | string | "love", "like", "dislike", "laugh", "emphasize", "question", "eyes", or "custom" |
custom_emoji | string | null | The literal emoji when reaction is "custom"; null for named outbound reactions, including "eyes" |
remote_number | string | The human's phone number (E.164) |
part_index | integer | Message part the tapback targets (0 for single-part messages) |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
The compact reaction entries embedded in a message's reactions array omit the conversation/assignment/target IDs (they're implied by the message) and the updated_at field.