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 · question

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

On this page