Reactions
Reacting with a tapback, removing one, and reading the outcome.
One command reacts through /v1: reaction.tapback.
It works in a conversation the contact has written into, targets a message you
name by its externalId, and removes a reaction as well as adding one.
The classic six
reaction.tapback takes six reaction names, which are the tapbacks Messages
itself offers:
love · like · dislike · laugh · emphasis · questionAnything outside that set fails, and where depends on the environment. On a
live line an unrecognised reaction on reaction.tapback is a
422 validation_failed before anything is queued. In test nothing checks it, so it comes back 202. An arbitrary
emoji cannot be sent — see Custom emoji.
Reacting to a message
You react to a message by its externalId, which is the iMessage GUID that
arrives on the message event, or by its own
messageId where the contact sent it. On a test line a tapback names its
message the same way, and the externalId there is the simulated device's own
id for the message. Keep that value alongside the conversationId when you
process inbound messages; you need both to react.
On an iMessage line, live or test, both are checked before anything is sent,
and each refusal is 422 validation_failed: a conversation the transport has
not named — it names one when the contact first writes into it — at
target.conversationId, and a messageId for a message the line sent, which
carries no iMessage id to name, or for one another conversation holds, at
payload.targetMessageExternalId.
curl https://api.mapier.ai/v1/commands/reaction.tapback \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: react-guid-1-love" \
-d '{
"target": {
"kind": "conversation",
"conversationId": "a0000000-0000-4000-8000-000000000001"
},
"payload": { "reaction": "love", "targetMessageExternalId": "guid-1" }
}'Reactions honour the Idempotency-Key above: a retry adopts the original
command rather than reacting twice. A key
built from the message id and the reaction, as here, is naturally unique per
intent — but the constraint is permanent, so if you later remove that reaction
and want to put it back, the same key adopts the finished command and nothing
happens. Add something that changes to the key whenever a repeat is a real
intent rather than a retry. See Idempotency.
The response is a 202 — the command is queued, not applied. The command
outcome arrives on the response stream. A
tapback a contact puts on or takes off a message reaches you as a
reaction.added or reaction.removed webhook, never a
stream frame, carrying messageId, conversationId, actor, emoji,
providerReactionId and occurredAt. Changing a tapback is a
reaction.added for the new one and a reaction.removed for the old, at the
same occurredAt. A tapback in a group chat is not delivered yet. Do not increment a reaction tally
on the command frame — it says whether your command finished, not what the
thread shows.
React promptly
stale_target is the failure you will actually hit. The line checks that the target GUID is still
present in the conversation's bounded history before it fires, and fails closed if it is not.
React while the message is recent — a reaction issued hours later, or after a long backlog, may
find nothing to attach to.
Removing a reaction
Same command, same target message, plus remove:
{ "reaction": "love", "targetMessageExternalId": "guid-1", "remove": true }You must name the reaction you are removing, not only the message.
Custom emoji
A reaction is one of the classic six, so a custom-emoji reaction cannot be sent.
Reading the outcome
A command frame settles each reaction you sent, with its status,
errorCode and failureClass. It names no actor, target or reaction, so the
frame cannot be correlated to a specific tapback by those fields — correlate it
by commandId. The reaction.added and reaction.removed webhooks are the
thread's tapbacks, not settlements of yours.
A reaction that was added, one that was removed, and one that was already in the
state you asked for all settle the command the same way: succeeded, with
errorCode: null. There is no endpoint that returns a message's full reaction
state.
Genuine failures arrive as status: "failed" with an errorCode. The two worth
handling are stale_target (the target aged out, or is not in the history of
the conversation the transport named) and unsupported_capability (the line
does not offer this reaction command). A line that offers it but not with the
options you used settles capability_constraint_violation — see
Capabilities.
tapback_unverified is a different case: it arrives on status: "ambiguous",
and means the reaction may have been applied but could not be confirmed.
Reapplying is harmless for an add — the end state is the same either way — but
do not treat it as a failure in a counter or an audit trail. Treat any
errorCode you do not recognise as a generic failure; the list is not closed.
See Errors.