← Все статьи
Руководство

Как безопасно сменить секрет подписи webhook в UnifyPort

Чтобы сменить signing_secret webhook в UnifyPort, сначала подготовьте все экземпляры получателя к проверке текущим и новым секретом. Затем обновите endpoint, убедитесь, что новая доставка проверяется новым ключом, и удалите старый после контрольных проверок. Это переход, которым управляет ваше приложение: публичный API предусматривает один секрет подписи на endpoint, а не серверный период совместного действия ключей или гарантию смены без потерь.

Главное

  • Меняйте секрет webhook отдельно от ключа REST API.
  • Не отправляйте пустой signing_secret как промежуточный шаг: это отключает подпись.
  • Ограничьте временный набор ключей одним endpoint и окружением.
  • Не считайте повторные доставки льготным периодом для развёртывания.

Разделите назначение ключей и границы сбоя

X-Api-Key аутентифицирует ваши запросы к UnifyPort. Параметр signing_secret позволяет проверять доставки в ваше приложение. Смена одного значения не заменяет другое. Для REST-клиентов используйте отдельную инструкцию по ротации API-ключа.

Документация доставки webhook определяет X-Device-Signature как HMAC-SHA256 в шестнадцатеричном формате. Подписывается последовательность из X-Device-Timestamp в формате RFC 3339, точки и исходных байтов тела запроса. При ротации меняется ключ HMAC, а не эта последовательность или схема события.

Несовпадение ключа у получателя может привести к отклонению настоящих событий. По текущему контракту ошибки соединения и ответы HTTP 408, 429 и 5xx вызывают немедленные повторные попытки без задержки; остальные 4xx не повторяются. Поэтому нельзя рассчитывать, что 401 из-за преждевременной смены ключа автоматически исправится после следующего развёртывания. Ответ 503 также не создаёт надёжную очередь ожидания на время обслуживания.

Спланируйте три конфигурации

Это рекомендуемая последовательность развёртывания, а не встроенная функция ротации UnifyPort.

ЭтапНастройка endpointПроверка у получателя
ПодготовкаТекущий секретТекущий и новый секреты
ПереключениеНовый секретТекущий и новый секреты
ЗавершениеНовый секретТолько новый секрет

До начала зафиксируйте ID endpoint, URL, статус, подписки на события, политику повторов, экземпляры получателя и ответственного за изменение. Значения секретов храните в системе управления секретами, а не в журнале изменений. Создайте независимый новый ключ и распространите его через обычный защищённый механизм развёртывания.

Не меняйте одновременно URL, подписки и настройки повторов. Перенос точки приёма вместе со сменой аутентификации затрудняет диагностику. Если старый секрет мог быть раскрыт, обычное совместное использование не подходит: пока вы принимаете этот ключ, риск сохраняется. Действуйте по плану реагирования на инцидент с явными решениями по доступности и сверке данных.

Сначала подготовьте всех получателей

Задайте небольшой временный набор ключей в доверенной конфигурации маршрута. Не выбирайте ключ по непроверенным provider, account_id или придуманному заголовку версии ключа. Документированные заголовки доставки не содержат идентификатора ключа подписи.

Пример ниже проверяет всех кандидатов, не завершая работу после первого совпадения. Это вспомогательная функция, а не готовый HTTP-сервер и не результат проведённых испытаний. keys должен быть непустым массивом непустых строк-секретов для данного endpoint, а maxAgeMs — выбранным приложением положительным конечным допуском времени.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyDuringRotation({
  rawBody, timestamp, signature, keys, maxAgeMs,
}) {
  if (!Array.isArray(keys) || keys.length === 0 ||
      keys.some(key => typeof key !== 'string' || key.length === 0) ||
      !Number.isFinite(maxAgeMs) || maxAgeMs <= 0) {
    throw new Error('Invalid webhook verification configuration');
  }
  if (!Buffer.isBuffer(rawBody) || typeof timestamp !== 'string' ||
      typeof signature !== 'string' || !/^[0-9a-f]{64}$/i.test(signature)) {
    return false;
  }
  const signedAt = Date.parse(timestamp);
  if (!Number.isFinite(signedAt) ||
      Math.abs(Date.now() - signedAt) > maxAgeMs) return false;

  const supplied = Buffer.from(signature, 'hex');
  let matches = 0;
  for (const key of keys) {
    const expected = createHmac('sha256', key)
      .update(timestamp + '.').update(rawBody).digest();
    matches |= Number(timingSafeEqual(supplied, expected));
  }
  return matches !== 0;
}

Эти операции HMAC и сравнения описаны в справочнике Node.js Crypto. Сохраняйте исходные байты и отклоняйте запросы без подписи. Не добавляйте запасной режим приёма неподписанных запросов. После проверки выполняйте валидацию payload и надёжное сохранение. Руководство по защите HMAC от повторного воспроизведения объясняет, почему даже совпавшая подпись не отменяет проверки свежести и защиты от повторной обработки.

Для каждого развёртывания получателя проверьте текущий ключ, новый ключ, неверный ключ, изменённое тело, отсутствие подписи и устаревшую временную метку. Это предлагаемые приёмочные тесты, а не утверждение, что код уже проверен в вашей инфраструктуре.

Обновите endpoint, не отключая подпись

Прочитайте текущую конфигурацию через Get webhook endpoint. Затем используйте Update webhook endpoint: PATCH /v1/webhook-endpoints/{endpoint_id} с аутентификацией ключом REST API.

Сформируйте обновление на основе проверенных текущих URL, активного статуса, подписок и политики повторов, указав новый секрет в signing_secret. Не переносите пустой секрет или неактивный статус из иллюстративного примера документации в рабочее переключение. Пустой секрет отключает подпись, а не запрашивает автоматическую ротацию.

Повторно прочитайте конфигурацию и проверьте signing_enabled: true, а также неизменность остальных настроек. Этот флаг подтверждает включённую подпись, но не показывает, какой секрет используется. Отправьте контролируемое тестовое сообщение в подключённый аккаунт обмена сообщениями и проследите новую доставку message.received: проверку новым ключом, надёжное сохранение и ожидаемую внутреннюю обработку. Не записывайте ключи в логи и не используйте клиентский контент как тестовые данные.

Если результат обновления неоднозначен, сохраните проверку двумя ключами, пока изучаете конфигурацию и проверяете новую доставку. Сам факт отправки PATCH не является основанием для удаления старого ключа.

Задайте условия завершения, а не произвольное время ожидания

Публичная документация не уточняет, сохраняют ли уже поставленные в очередь попытки старую конфигурацию подписи. Она также не задаёт максимальный период совместного приёма ключей. retry_policy.max_attempts считает повторы, а не секунды льготного периода. Прежде чем обещать непрерывную смену, уточните у UnifyPort неизвестное поведение доставок в процессе отправки.

Условиями завершения должны быть готовность всех экземпляров, новые доставки с новым ключом, наблюдения за ошибками и согласованный порядок работы с незавершёнными доставками. В эксплуатационных метриках храните только несекретные метки версии проверки. Отсутствие трафика само по себе не доказывает, что доставки со старым ключом закончились.

После прохождения этих проверок удалите старый секрет со всех получателей и из источников конфигурации развёртывания. В изолированном тесте убедитесь, что он теперь отклоняется. Ограничьте совместное использование явным эксплуатационным решением, а не принимайте исторические ключи бессрочно.

Если нужен откат и старый ключ остаётся доверенным, согласованно меняйте настройку endpoint и набор ключей получателя. Нельзя возвращать получателя, принимающего только старый ключ, пока endpoint подписывает новым. При подозрении на раскрытие не восстанавливайте скомпрометированный ключ.

Область применения и ограничения

Неофициальный интерфейс UnifyPort нормализует события поддерживаемых аккаунтов обмена сообщениями. Эта процедура защищает передачу от UnifyPort к вашему получателю. Она не меняет нативные учётные данные Telegram или LINE, сессии провайдеров и API-ключи.

Сохраняйте аутентифицированные события до ответа 2xx. Для обычных событий повторы можно устранять по ID события; WhatsApp conversation.history требует объединения на уровне сообщений согласно контракту, а не глобального отбрасывания по верхнеуровневому ID. UnifyPort не предоставляет REST API чтения истории сообщений и не гарантирует повторную выдачу пропущенных payload. Поэтому сбои ротации нельзя описывать как автоматически восстанавливаемые.

FAQ

Можно ли задать два signing_secret одному endpoint?

Публичный контракт содержит один signing_secret. Временная проверка двумя ключами из этой статьи реализуется в вашем приложении, а не настройкой двух секретов в API.

Можно ли ненадолго отключить подпись для простоты?

Не следует. Оставьте подпись включённой и отклоняйте запросы без аутентификации. Пустой секрет удаляет заголовок подписи, а не предоставляет механизм перехода.

Как долго принимать старый ключ?

Универсальный документированный интервал отсутствует. Используйте проверки развёртывания и доставки, уточните поведение незавершённых попыток и задайте ограниченные условия завершения с учётом риска.

Следующий шаг и источники

Изучите Update webhook endpoint и отрепетируйте три конфигурации в изолированной среде до изменения production.

Документация проверена 2026-09-27:

UnifyPort API

Превратите интеграцию сообщений в стабильный продуктовый pipeline.

Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.