Как обрабатывать реакции на сообщения в едином Webhook
Чтобы обработать реакцию, подпишитесь на message.reaction, проверьте подпись Webhook по исходному телу запроса и используйте data.message.target_message_id как идентификатор сообщения, к которому относится реакция. Эмодзи находится в data.event.reaction, а пустая строка означает удаление. Перед обновлением состояния исключите повторную доставку по идентификатору события верхнего уровня.
Главное
data.message.idидентифицирует саму реакцию, а не исходное сообщение.data.message.target_message_idуказывает на сообщение, к которому добавили реакцию.data.event.reactionсодержит эмодзи;""означает удаление.- Проверяйте
X-Device-Signatureдо разбора JSON. - Поддержка зависит от платформы: корректное имя события не гарантирует, что каждый провайдер его создаёт.
Структура message.reaction
UnifyPort представляет реакции стандартным событием message.reaction. Приложение может считать 👍 подтверждением, направлять 👎 на проверку оператору или просто показывать актуальный эмодзи рядом с сообщением. Это правила вашего процесса; само событие сообщает только о произошедшем изменении.
В официальном справочнике стандартных Webhook-событий приведена такая структура message.reaction:
{
"id": "evt_2f9c1a4b7e",
"type": "message.reaction",
"provider": "whatsapp",
"account_id": "acc_8c21d0",
"occurred_at": "2026-06-08T12:35:40Z",
"data": {
"conversation": { "id": "8613912345678", "type": "user" },
"sender": { "id": "8613912345678", "type": "user", "name": "Jordan Lee" },
"message": { "id": "wamid.HBgZ", "target_message_id": "wamid.HBgM" },
"event": { "kind": "message_reaction", "reaction": "👍" }
}
}
У трёх идентификаторов разные задачи. data.message.id обозначает запись реакции, data.message.target_message_id — исходное сообщение, а верхнеуровневый id — стандартное событие Webhook. В этом примере 👍 нужно связать с wamid.HBgM, а не с wamid.HBgZ.
Храните текущее состояние, а не только журнал
Журнал только для добавления удобен для аудита и диагностики, однако интерфейсу обычно требуется актуальное состояние. Практичный ключ состояния включает:
provideraccount_iddata.conversation.iddata.message.target_message_iddata.sender.id
Если data.event.reaction не пусто, сохраните текущий эмодзи отправителя для целевого сообщения. Если значение — пустая строка, удалите соответствующую реакцию. Не сохраняйте пустую строку как новый тип реакции.
До изменения проекции запишите событие в надёжную очередь или базу и добавьте уникальное ограничение на верхнеуровневый id. Это не позволит повторной доставке дважды применить одну мутацию. Если текущий приёмник подписан только на входящие сообщения, руководство по фильтрам Webhook показывает, как добавить message.reaction без немедленного перехода на wildcard.
Применение события в Node.js
Ниже используются реальные поля из API reference. Map служит для демонстрации; в production замените его таблицей с транзакциями и уникальными ограничениями.
const event = JSON.parse(rawBody.toString('utf8'));
if (event.type === 'message.reaction') {
const { conversation, sender, message, event: detail } = event.data;
if (!message?.target_message_id || typeof detail?.reaction !== 'string') {
throw new Error('Invalid message.reaction payload');
}
const key = [event.provider, event.account_id, conversation.id,
message.target_message_id, sender.id].join(':');
if (detail.reaction === '') reactionState.delete(key);
else reactionState.set(key, {
emoji: detail.reaction,
reactionMessageId: message.id,
occurredAt: event.occurred_at,
});
}
Эта логика должна выполняться только после проверки подписи. Если для endpoint задан signing_secret, X-Device-Signature представляет собой HMAC-SHA256 в шестнадцатеричном виде для строки <X-Device-Timestamp>.<исходное тело запроса>. Нельзя сначала разобрать JSON и сериализовать его заново. Полная процедура приведена в руководстве по доставке и подписи Webhook, а время, повторные доставки и постоянная идемпотентность подробнее разобраны в руководстве по HMAC и безопасным повторам.
Подписка и обработка ошибок
Для обработчика только реакций подходит настройка:
{
"subscribed_events": ["message.reaction"]
}
Единый inbox обычно подписывается на message.received и message.reaction. Значение "*" подходит только универсальному сборщику, который готов принимать все публичные стандартные события.
Возвращайте 2xx после надёжного приёма события. Запрос с неверной подписью нужно отклонить, а структурно некорректное событие направить в наблюдаемый поток ошибок. Прежде чем делать эмодзи единственным сигналом согласования, проверьте матрицу событий по провайдерам.
Роль UnifyPort
UnifyPort предоставляет неофициальный интерфейс и нормализует события поддерживаемых платформ в единый конверт. Приложение может маршрутизировать данные по event.type и повторно использовать одну логику там, где доступен message.reaction.
Нормализация не создаёт функцию, отсутствующую у исходной платформы. Если реакции обязательны для аудита, согласования или учёта, проверьте поддержку для каждой платформы и выберите официальный API там, где его контракт лучше соответствует требованиям.
FAQ
Какое поле указывает на исходное сообщение?
data.message.target_message_id. Поле data.message.id идентифицирует саму реакцию.
Как определить удаление реакции?
Проверьте data.event.reaction: пустая строка означает удаление, непустая содержит текущий эмодзи.
Можно ли устранять дубли по target_message_id?
Нет. На одно сообщение могут реагировать разные люди, а один пользователь может менять эмодзи. Для повторной доставки используйте верхнеуровневый id.
Все ли платформы отправляют message.reaction?
Нет. Валидное имя события и фактическая поддержка провайдера — разные вещи. Перед запуском проверьте матрицу событий.
Следующий шаг
Откройте справочник создания Webhook endpoint, добавьте message.reaction в subscribed_events, настройте signing_secret и протестируйте как добавление, так и удаление эмодзи.
Источники
Официальные первичные источники, проверено 20 августа 2026 года: