Большинство багов вебхуков Shopify в локальной разработке сводятся к одной строке: HMAC кодируется в base64, а не hex. Если вы вызываете .digest('hex') и сравниваете с X-Shopify-Hmac-Sha256, проверка никогда не пройдёт — даже с правильным общим секретом. Мы видели, как senior-инженеры теряли полдня из-за этого одного символа.
Ловушка base64
Stripe использует hex. GitHub использует hex (с префиксом sha256=). Shopify использует base64. Три провайдера, три кодировки. Ваша первая версия верификатора почти наверняка скопирует сниппет из другого проекта и тихо провалится.
Вот как должна выглядеть проверка:
const hmac = crypto
.createHmac('sha256', SHOPIFY_API_SECRET)
.update(rawBody) // raw body, not JSON-parsed
.digest('base64'); // base64, not hex
const valid = crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(req.headers['x-shopify-hmac-sha256']),
);
Помимо кодировки важны две вещи. Тело должно быть сырыми, непарсенными байтами. И сравнение должно быть timing-safe — равенство строк утекает информацию по байту за раз.
Почему туннель упрощает это
Можно также использовать команду Shopify shopify app dev, которая поднимает внутренний туннель. Работает. Но она оборачивает ваш dev-сервер в собственный процесс, проглатывает логи определённым образом и не даёт кнопки повтора. Для разработки приложения сложнее «hello world» стабильный публичный URL плюс туннель localhost с захватом запросов экономит больше времени, чем CLI экономит на начальной настройке.
npx portpreview 3000
Вы вставляете HTTPS-URL в конфигурацию вебхуков приложения в дашборде Partners, триггерите событие из тестового магазина, и запрос попадает в ваш локальный обработчик со всеми заголовками нетронутыми.
Тестовые магазины: часть, которую документация Shopify пробегает
Две вещи, которые нужно знать, прежде чем что-то подключать:
- Магазины разработки шлют все события вебхуков. Создание заказа, фулфилмент, инвентарь — всё. Не нужно ничего подделывать; просто установите приложение на dev-магазин и кликайте.
- Секрет вебхука отличается по приложению и по каналу доставки. Если вы также подписаны на EventBridge или Pub/Sub, поведение HMAC другое. Здесь речь о простой HTTPS-доставке вебхуков.
Пошаговая настройка
- Запустите приложение локально на нужном порту (3000 — частый выбор).
- Выполните
npx portpreview 3000и скопируйте напечатанный HTTPS-URL. - В дашборде Shopify Partners откройте приложение и перейдите в Configuration → Webhooks.
- Установите endpoint вебхука на
https://your-tunnel.portpreview.dev/api/webhooks/shopify(или путь, который использует приложение). - Установите приложение на dev-магазин, затем триггерните событие — создайте черновик заказа, выполните товар, измените инвентарь.
- Смотрите, как запрос попадает. Инспектируйте заголовки и тело. Повторяйте захваченный запрос после каждого фикса обработчика.
Ошибки, которые мы постоянно видим
Тело распарсено до верификации
Если вы ставите app.use(express.json()) перед маршрутом вебхука, сырые байты пропадают к моменту проверки. Монтируйте парсер сырого тела только на путь вебхука или вытаскивайте тело вручную из потока запроса до любого JSON-парсинга.
Перепутанные секреты
Секрет приложения Partners — не то же, что токен Storefront API. HMAC вебхука использует секрет приложения. Если вы смотрите на shp_xxx в env-файле, вы взяли не тот.
Тестовые события, выглядящие одинаково
Кнопка Shopify «Send test notification» доставляет синтетическое событие с телом-заглушкой. Подпись на этом тестовом событии настоящая, но payload фиксирован. Для реалистичного тестирования формы payload триггерите события из самого dev-магазина.
Повтор не обсуждается для разработки приложений
Shopify повторяет неудачные вебхуки до 48 часов с экспоненциальным backoff. Это щедро, но при итерациях вы не хотите ждать следующего повтора. Захватите первую доставку в истории запросов вашего туннеля и повторяйте по требованию против локального обработчика.
Когда локального становится мало
Потоки регистрации магазина, GDPR-вебхуки и изменения биллинга подписок проще валидировать против развёрнутой среды, потому что они взаимодействуют с собственным состоянием аккаунта Shopify. Для всего остального — парсинг payload, верификация подписи, бизнес-логика — локально быстрее.
По лежащей в основе математике подписи для всех провайдеров прочтите наше руководство по верификации подписи вебхуков. Запишитесь в лист ожидания PortPreview, если хотите туннель со встроенным повтором.