Вебхуки
Асинхронные уведомления о завершении сессии парсинга вместо периодического опроса API — подписка, проверка доставки и пример интеграции с n8n.
Вместо того чтобы опрашивать GET /me/scrapes/{id}, пока status не станет completed,
можно подписать URL, и мы сами отправим на него POST-запрос в момент завершения. Пока
существует одно событие: scrape_session.completed.
POST /webhooks, который можно было бы вызвать самостоятельно, нет. До 5 активных подписок на аккаунт.Тело события — это указатель, а не сами данные
{
"event": "scrape_session.completed",
"user_id": "3e2f9a10-df34-4b8a-9c31-8e1d2f6a7b90",
"scrape_session_id": "b1f8b6b0-2f7d-4a1a-9a3c-2f6d8e9a1234",
"status": "completed",
"stats": { "total": 40, "pending": 0, "processing": 0, "merged": 38, "rejected": 1, "flagged": 1 },
"url": "https://data.marketninja.ru/v1/me/scrapes/b1f8b6b0-2f7d-4a1a-9a3c-2f6d8e9a1234/products"
}Мы намеренно не встраиваем спарсенные данные прямо в событие — чтобы их получить,
обратитесь по полю url (та же авторизация Bearer, что и везде на этом сайте, и запрос
расходует вашу обычную дневную квоту — точно так же, как прямой вызов этого эндпоинта).
Причины две: дневная квота на данные проверяется только на этом одном эндпоинте, и
компактное тело события работает одинаково независимо от того, было в сессии 1 товар или
10 000.
user_id показывает, какому из ваших аккаунтов Market Ninja принадлежит событие —
пригодится, если вы когда-нибудь направите вебхуки нескольких аккаунтов на один и тот же
URL (например, общий эндпоинт автоматизации) и вам нужно будет их различать.
Проверка доставки
Каждый запрос содержит два заголовка:
X-Marketninja-Event: scrape_session.completedX-Marketninja-Signature: t=1735689600,v1=5257a869e7bcd1d2...
Подпись — это HMAC-SHA256 от ${timestamp}.${rawBody} с использованием секретного ключа,
который был показан один раз при создании подписки. Проверяйте её по исходному
телу запроса, полученному как есть — никогда по повторно сериализованному
JSON.stringify(JSON.parse(body)): это не гарантирует побайтовое совпадение (порядок
ключей, пробелы) и молча сломает проверку.
import crypto from "node:crypto";
function isValidSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("="))
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(parts.v1),
Buffer.from(expected)
);
}t не слишком старый (обычно достаточно допуска в несколько минут), если нужна защита от повторного воспроизведения запроса — на нашей стороне такой допуск не применяется.Доставка, повторные попытки и когда мы сдаёмся
- Любой ответ
2xxсчитается успешной доставкой — тело ответа не читается. - Мы не следуем за редиректами —
3xxобрабатывается точно так же, как явная ошибка. - Каждая попытка ограничена таймаутом в 5 секунд.
- Неудачная попытка (сетевая ошибка, таймаут,
429или5xx) повторяется по расписанию с задержкой: примерно через 1 мин, 5 мин, 30 мин, 2ч, 6ч, затем через 24ч — до 7 попыток всего, это около полутора суток — после чего конкретная доставка помечается как окончательно неудачная. Если в ответе429есть заголовокRetry-After, используется он, а не фиксированное расписание. - Любой другой ответ, отличный от
2xx(например,400,404), считается окончательной неудачей сразу — мы исходим из того, что повтор не изменит решение вашего эндпоинта. - Если у подписки накопится достаточно подряд идущих неудачных попыток, она автоматически отключается — включить отключённую подписку обратно нельзя, можно только создать новую на странице настроек расширения.
https:// — мы отклоняем target_url, который резолвится в приватный/loopback/link-local адрес, уже на этапе создания подписки (и перепроверяем перед каждой доставкой). Для тестирования локального сервера сначала понадобится туннель (ngrok, Cloudflare Tunnel и т.п.).Пример: запуск LLM-анализа цен в n8n
Частый сценарий: получить уведомление, забрать данные, передать их LLM, отправить результат куда-нибудь. Специфичны для Market Ninja только два узла; всё, что после них — целиком на ваше усмотрение.
-
Узел Webhook в качестве триггера —
HTTP Method: POST, путь любой на ваш выбор. Именно этот URL вы регистрируете как цель подписки. -
Узел HTTP Request сразу после него, настроенный на
GET-запрос по полюurlиз пришедшего события, с Bearer-авторизацией:- URL:
{{ $json.body.url }} - Authentication: Generic Credential Type → Bearer Auth, с сохранённым credential,
содержащим один из ваших ключей
mn_live_....
Ответ имеет вид
{ data: [...], meta: {...} }— тот же формат, что всегда возвращаетGET /me/scrapes/{id}/products: по одной записи на каждый сабмишен, сproduct(null, если ещё не слит). - URL:
-
Дальше делайте с
$json.dataто, что вам действительно нужно — узел LLM для сводки по ценам, уведомление в Slack/Telegram, запись в базу данных, или всё это вместе. Это уже никак не связано с Market Ninja — настраивается как в любом другом workflow n8n.
200, независимо от того, успешно ли отработает остальной workflow. Статус «доставлено» на нашей стороне подтверждает только то, что ваш узел Webhook получил уведомление — успех самой автоматизации проверяйте по истории выполнения своего workflow.Last updated on