WebScript (уведомления прямо со страницы)
WebScript — это лёгкий способ получать уведомления прямо с чужой/своей
веб-страницы, ничего не трогая в бекенде. Вы создаёте «скрипт» в Notifly,
указываете тип события (открытие страницы / отправка формы / клик по кнопке /
ошибки консоли) — и получаете готовый HTML-сниппет с публичным URL вида
/script/T<token>.
Вставляете его в шаблон сайта, в виджет, в письмо, в README — каждый раз, когда событие срабатывает, в выбранный канал прилетает уведомление.
Удобно для:
- сайтов на «коробочной» CMS, где сложно дотянуться до бекенда;
- лендингов и форм на статике (Tilda, GitHub Pages, Netlify);
- быстрых экспериментов («хочу пинг каждый раз, когда кто-то жмёт Купить»);
- виджетов в админках сторонних SaaS, куда можно вставить только
<script>; - замены Sentry/Bugsnag для pet-проектов и небольших команд.
Типы скриптов
Заголовок раздела «Типы скриптов»Поддерживается четыре предустановленных шаблона. Сниппет с готовым HTML/JS вы скопируете прямо из админки — здесь только описание поведения.
scriptType | Когда срабатывает | Что попадает в message |
|---|---|---|
page_open | При загрузке страницы (<script> исполняется) | Страница: <title> + URL: <location> |
form_submit | При сабмите любой <form> на странице | Все непустые поля формы (name: value) |
button_click | При клике на элемент с атрибутом data-notifly | Текст атрибута data-notifly или текстовое содержимое + URL |
console_errors | При возникновении JS-ошибки в браузере | Стектрейс, URL, User-Agent, lineno/colno |
Все четыре варианта отправляют POST на /script/T<token> с application/json-
телом {title, message} — это публичный эндпоинт, не требующий авторизации
(аутентификацией служит сам T<token> в URL).
Создание скрипта
Заголовок раздела «Создание скрипта»Через админку
Заголовок раздела «Через админку»- Откройте app.notifly.ru → Веб-скрипты.
- Нажмите «Создать скрипт», заполните:
- Название — для отображения, например «Лендинг — клик “Купить”».
- Канал — куда слать уведомления.
- Тип события —
Открытие страницы,Отправка формыилиКлик на кнопку. - Заголовок уведомления —
titleсообщения по умолчанию. - Приоритет — 0 = взять
defaultPriorityканала, иначе 1–10.
- После создания нажмите «Показать сниппет» — там готовый HTML, который
можно скопировать в
<head>сайта или в любое место<body>.
Через REST API
Заголовок раздела «Через REST API»curl -X POST "$NOTIFLY_URL/web-script" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{ "name": "Лендинг — клик «Купить»", "appId": 12345, "scriptType": "button_click", "title": "Клик «Купить» на лендинге", "priority": 7 }'Для console_errors:
curl -X POST "$NOTIFLY_URL/web-script" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{ "name": "Production — ошибки фронтенда", "appId": 12345, "scriptType": "console_errors", "title": "JS Error", "priority": 8 }'В ответе придёт объект скрипта с публичным token (префикс T):
{ "id": 8, "token": "T7c2a8f3b1e0d4a6c8e9f", "appId": 12345, "appName": "Marketing", "name": "Лендинг — клик «Купить»", "scriptType": "button_click", "title": "Клик «Купить» на лендинге", "priority": 7, "created": "2026-04-30T10:11:12Z", "lastUsed": null}Полный URL для триггера — ${NOTIFLY_URL}/script/T7c2a8f3b1e0d4a6c8e9f.
Готовые сниппеты
Заголовок раздела «Готовые сниппеты»Все три сниппета — вариации одного и того же fetch(url, {method: "POST", ...}).
Они никогда не падают на стороне сайта (.catch(function(){})) и не
требуют CORS-настроек, потому что /script/:token отвечает 200 OK всегда.
page_open — пинг при открытии страницы
Заголовок раздела «page_open — пинг при открытии страницы»<!-- Notifly — уведомление при открытии страницы --><script>(function() { fetch("https://your-notifly/script/T7c2a8f3b1e0d4a6c8e9f", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ title: "Лендинг — клик «Купить»", message: "Страница: " + document.title + "\nURL: " + location.href }) }).catch(function(){});})();</script>form_submit — пинг при отправке формы
Заголовок раздела «form_submit — пинг при отправке формы»Скрипт навешивается на все <form> на странице, собирает все непустые
поля и отправляет их в теле уведомления:
<script>document.addEventListener("DOMContentLoaded", function() { document.querySelectorAll("form").forEach(function(form) { form.addEventListener("submit", function() { var fd = new FormData(form); var lines = []; fd.forEach(function(v, k) { if (v) lines.push(k + ": " + v); }); fetch("https://your-notifly/script/T...", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ title: "Заявка с сайта", message: lines.join("\n") || "(пустая форма)" }) }).catch(function(){}); }); });});</script>button_click — пинг при клике на элемент
Заголовок раздела «button_click — пинг при клике на элемент»Срабатывает только для элементов, у которых есть атрибут data-notifly:
<script>document.addEventListener("click", function(e) { var el = e.target.closest("[data-notifly]"); if (!el) return; var label = el.getAttribute("data-notifly") || el.textContent.trim() || "кнопка"; fetch("https://your-notifly/script/T...", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ title: "Клик «Купить»", message: "Клик: " + label + "\nСтраница: " + location.href }) }).catch(function(){});});</script><!-- Пример использования: --><button data-notifly="Заказать">Заказать</button>console_errors — перехват ошибок консоли (Sentry-lite)
Заголовок раздела «console_errors — перехват ошибок консоли (Sentry-lite)»Самый мощный тип скрипта — полноценный перехватчик JS-ошибок, работающий как
облегчённый аналог Sentry / Bugsnag / TrackJS. Для него используется hosted SDK
(error-sdk.js), который Notifly раздаёт со своего CDN — готовый сниппет с вашим
токеном генерируется в админке на карточке скрипта («Примеры кода»). SDK ловит:
window.onerror— все необработанные исключения (включая ошибки загрузки ресурсов);unhandledrejection— отклонённые промисы без.catch();- ручные вызовы —
ErrorSDK.captureException(err, ctx)иErrorSDK.captureMessage(msg, level, ctx).
Особенности:
- Breadcrumbs — SDK буферизует события
console.*,fetch/XHR, клики и SPA-навигацию; последние крошки прикладываются к ошибке («что было перед ней»). - Батчинг — ошибки копятся в очередь и отправляются пачкой (до 10 шт или каждые 3 сек).
- Дедупликация — одна и та же ошибка (по fingerprint) не отправляется повторно в окне ~10 сек.
- Sampling —
sampleRate(1 = всё) прореживает отправку на клиенте; сервер экстраполирует счётчики. - Лимит запросов — максимум 100 POST-ов за загрузку страницы, чтобы не попасть в бесконечный цикл.
- sendBeacon — используется
navigator.sendBeaconпри уходе со страницы (не блокирует навигацию). - Финальный flush — при
pagehide/ скрытии вкладки скрипт отправляет оставшуюся очередь. - Санитизация — секреты (password/token/cookie/…) и чувствительные query-параметры вырезаются и на клиенте, и повторно на сервере.
- 1 батч = 1 событие квоты — весь массив ошибок превращается в одно уведомление с одним push.
<!-- Notifly — перехват ошибок браузера (hosted SDK) --><script src="https://app.notifly.ru/static/error-sdk.js" crossorigin="anonymous"></script><script> // window.NOTIFLY_RELEASE можно задать ДО этого скрипта, чтобы помечать события релизом. window.ErrorSDK && window.ErrorSDK.init({ endpoint: "https://your-notifly/script/T...", app: "My App", release: (window.NOTIFLY_RELEASE || ""), environment: "production", sampleRate: 1, maxBreadcrumbs: 20 });</script>Параметры init():
| Параметр | Описание |
|---|---|
endpoint | Публичный URL скрипта POST /script/T<token> (обязателен) |
app | Название приложения — попадает в заголовок уведомления |
release | Метка релиза (git-sha / версия) — нужна для source maps |
environment | Окружение (production / staging / …) — сверяется с allowlist скрипта |
sampleRate | Доля отправляемых ошибок, 1 = все |
notifyThreshold | Клиентская подсказка порога уведомлений (сервер всё равно решает сам) |
maxBreadcrumbs | Сколько «крошек» прикладывать к ошибке (≤ 30) |
Формат данных
Заголовок раздела «Формат данных»SDK формирует батч сам; ниже — схема для кастомных интеграций. Тело запроса
POST /script/T<token> (макс. 64 KiB, до 50 ошибок в батче):
{ "title": "My App", "release": "v1.2.3", "environment": "production", "sampleRate": 1, "flushReason": "batch", "errors": [ { "eventId": "3f2a…", "type": "error", "level": "error", "message": "Cannot read properties of undefined (reading 'map')", "stack": "TypeError: Cannot read properties...\n at App.tsx:42:12\n at ...", "url": "https://example.com/dashboard", "lineno": 42, "colno": 12, "userAgent": "Mozilla/5.0 ...", "ts": 1716556800000, "sentAt": "2026-07-14T12:00:00.000Z", "exception": {"name": "TypeError", "message": "...", "stack": "..."}, "runtime": { "url": "https://example.com/dashboard", "path": "/dashboard", "referrer": "https://example.com/", "userAgent": "Mozilla/5.0 ...", "language": "ru-RU", "viewport": "1920x1080" }, "context": {"feature": "checkout"}, "breadcrumbs": [ {"category": "ui.click", "level": "info", "message": "", "data": {"target": "button#pay"}, "timestamp": "2026-07-14T11:59:58.000Z"} ] } ]}Обязательные поля каждой ошибки: message, type (error | unhandledrejection |
console.error | exception | message), level (info | warning | error |
fatal), runtime.url и sentAt (RFC3339). Батч без environment, с неизвестным
type/level или с более чем 30 breadcrumbs на ошибку отклоняется с кодом 400.
Сервер Notifly формирует из батча одно уведомление в стиле Sentry-карточки:
- Заголовок — первая ошибка (
TypeError: …, +(+N more)если ошибок несколько). - Тело — тэги (level · environment · release · браузер · язык), URL:строка:колонка, referrer/viewport, стектрейс первой ошибки, компактный список остальных (до 4), последние breadcrumbs и счётчик повторений проблемы.
Привязка к релизу
Заголовок раздела «Привязка к релизу»Задайте window.NOTIFLY_RELEASE до загрузки скрипта — значение попадёт
в поле release каждого батча:
<script>window.NOTIFLY_RELEASE = "v2.1.0-abc1234";</script><!-- Notifly console_errors snippet here -->Куда вставлять в популярных фреймворках
Заголовок раздела «Куда вставлять в популярных фреймворках»| Фреймворк | Где размещать |
|---|---|
| Next.js (App Router) | app/layout.tsx в <head> через next/script strategy="beforeInteractive" |
| Next.js (Pages) | pages/_document.tsx в <Head> |
| React (CRA / Vite) | public/index.html в <head> перед бандлом |
| Vue / Nuxt | nuxt.config → app.head.script или app.html |
| Angular | src/index.html в <head> перед polyfills |
| Astro | src/layouts/Layout.astro в <head> |
| Electron | В renderer HTML <head> |
Source maps — раскрывание минифицированного стека
Заголовок раздела «Source maps — раскрывание минифицированного стека»Если ваш JS прошёл через минификатор (esbuild, terser, Webpack production), стектрейсы
в браузере выглядят как at f (https://app.example.com/static/js/main.abc123.js:1:12345) —
без имён функций и реальных строк. Notifly умеет на лету разворачивать такие фреймы
обратно к исходникам (TypeScript / .vue / .jsx), используя загруженные source map.
Как это работает
Заголовок раздела «Как это работает»- Соберите фронтенд с включёнными
.map-файлами (sourceMap: true). - Загрузите эти
.mapв Notifly через REST API — по одной карте на каждый.js-файл. - Укажите для каждого
.mapтот жеrelease, что прокидываете вwindow.NOTIFLY_RELEASE. - При получении ошибки Notifly находит
.mapпоrelease+ URL-префиксу + имени файла и подменяетURL:LINE:COLнаsrc/file.ts:LINE:COL— прямо в тексте уведомления.
Резолвинг — best-effort: если карта не найдена или фрейм не совпал, в уведомление попадает оригинальный (минифицированный) стек.
Загрузка через REST
Заголовок раздела «Загрузка через REST»# базовая аутентификация — те же логин/пароль, что для админкиcurl -u admin:admin \ -F "release=v2.1.0-abc1234" \ -F "urlPrefix=https://app.example.com/static/js/" \ -F "fileName=main.abc123.js" \ -F "file=@dist/static/js/main.abc123.js.map" \ "$NOTIFLY_URL/web-script/<scriptId>/sourcemaps"Тело запроса — multipart/form-data с обязательными полями:
| Поле | Описание |
|---|---|
file | Сам .map-файл (JSON Source Map v3, до 10 MB) |
release | Версия релиза — должна совпасть с window.NOTIFLY_RELEASE |
urlPrefix | Префикс URL, по которому браузер грузит этот .js (например https://x/static/) |
fileName | Имя .js-файла без префикса (main.abc123.js), без / и \ |
При совпадении кадра по urlPrefix + fileName Notifly резолвит позицию. Поиск
кадра — по длиннейшему префиксу, так что можно держать одновременно карты
с разных доменов / CDN.
CLI-хелпер
Заголовок раздела «CLI-хелпер»Чтобы не звать curl в цикле, используйте готовый скрипт
scripts/upload-sourcemaps.py:
python3 scripts/upload-sourcemaps.py \ --api https://api.notifly.ru \ --user "$NOTIFLY_USER" --password "$NOTIFLY_PASS" \ --script-id 12345 \ --release "$GITHUB_SHA" \ --url-prefix "https://app.example.com/static/js/" \ --dir ./dist/static/jsПример GitHub Actions — выгрузка карт после билда:
- name: Build run: npm run build
- name: Upload sourcemaps to Notifly env: NOTIFLY_USER: ${{ secrets.NOTIFLY_USER }} NOTIFLY_PASSWORD: ${{ secrets.NOTIFLY_PASSWORD }} run: | python3 scripts/upload-sourcemaps.py \ --script-id ${{ vars.NOTIFLY_SCRIPT_ID }} \ --release "${{ github.sha }}" \ --url-prefix "https://app.example.com/static/js/" \ --dir ./dist/static/jsУправление и REST
Заголовок раздела «Управление и REST»| Метод и путь | Назначение |
|---|---|
GET /web-script/:id/sourcemaps | список загруженных карт |
POST /web-script/:id/sourcemaps | загрузить .map (multipart) |
DELETE /web-script/:id/sourcemaps/:smid | удалить карту (S3 + БД) |
В админке кнопка «Source maps» появляется на карточке скрипта типа
console_errors — там же можно посмотреть список, удалить устаревшие карты
или загрузить новый .map без CLI.
Безопасность и лимиты
Заголовок раздела «Безопасность и лимиты»- Карты хранятся в приватном S3-бакете Yandex Object Storage, прямого URL у них нет.
- Содержимое дедуплицируется по
sha256— повторная заливка одного и того же файла не создаёт лишних копий. - Максимальный размер одного
.map— 10 MB. - При удалении web-скрипта все связанные
.mapудаляются автоматически (cascade). - Резолв ограничен таймаутом 3 секунды на запрос — если карт много, лишние игнорируются, оригинальный стек всё равно попадёт в уведомление.
Триггер вручную
Заголовок раздела «Триггер вручную»Вы можете дёрнуть /script/T<token> чем угодно — этот эндпоинт публичный
и принимает GET и POST. JSON-тело необязательно: если его нет, в push
уйдёт title из настроек скрипта и текст «(web script trigger)».
# минимальноcurl -fsS "$NOTIFLY_URL/script/T7c2a8f3b1e0d4a6c8e9f" -o /dev/null
# с переопределением заголовка и текстаcurl -X POST "$NOTIFLY_URL/script/T7c2a8f3b1e0d4a6c8e9f" \ -H "Content-Type: application/json" \ -d '{"title":"Заявка №42","message":"Имя: Иван\nТелефон: +7..."}'REST API
Заголовок раздела «REST API»| Метод и путь | Авторизация | Назначение |
|---|---|---|
GET /web-script | client-token | список скриптов |
POST /web-script | client-token (write) | создание |
PUT /web-script/:id | client-token (write) | обновление |
DELETE /web-script/:id | client-token (write) | удалить |
GET /web-script/:id/sourcemaps | client-token | список source map |
POST /web-script/:id/sourcemaps | client-token (write) | загрузить .map |
DELETE /web-script/:id/sourcemaps/:smid | client-token (write) | удалить .map |
GET /web-script/:id/issues | client-token | список агрегированных ошибок |
GET /web-script/:id/issue-count | client-token | число нерешённых проблем ({"unresolved": N}) |
POST /web-script/:id/issues/:iid/resolve | client-token (write) | отметить решённой |
POST /web-script/:id/issues/:iid/ignore | client-token (write) | игнорировать проблему |
DELETE /web-script/:id/issues/:iid | client-token (write) | удалить |
GET /web-script/:id/issues/:iid/breadcrumbs | client-token | последние действия пользователя перед ошибкой |
POST /web-script/:id/issues/:iid/analyze | client-token (write) | AI-разбор причины (тело: lang, force) |
GET /web-script/:id/issues/:iid/analysis | client-token | сохранённый разбор (204, если его ещё нет) |
GET /web-script/:id/series | client-token | тайм-серия срабатываний скрипта |
GET /web-script/:id/error-series | client-token | тайм-серия ошибок console_errors |
GET /web-script/:id/flush-stats | client-token | распределение причин отправки батчей |
GET/POST /script/:token | публичный | триггер из браузера или скрипта |
Раздел «Issues» на карточке скрипта console_errors группирует входящие
ошибки в дедуплицированные проблемы (по message + верхнему фрейму стека +
release): для каждой видно число повторений, время первого и последнего
появления. Проблему можно отметить решённой (/issues/:iid/resolve) — её
следующее появление снова даст уведомление как регрессия, — скрыть
(/issues/:iid/ignore) или удалить
(DELETE /web-script/:id/issues/:iid).
AI-разбор проблемы
Заголовок раздела «AI-разбор проблемы»POST /web-script/:id/issues/:iid/analyze собирает улики (стек, breadcrumbs,
контекст страницы), отдаёт их модели и сохраняет разбор — вероятную причину и
что проверить в первую очередь:
curl -X POST "$NOTIFLY_URL/web-script/77/issues/1234/analyze" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"lang": "ru"}'Разбор кэшируется по отпечатку входных данных: пока проблема не получила новых
событий и язык не менялся, повторный вызов отдаёт сохранённый результат — без
обращения к модели и без списания AI-квоты. Принудительно
пересчитать — {"force": true}. Готовый разбор доступен отдельно через
GET /web-script/:id/issues/:iid/analysis (204, если его ещё нет).
Графики активности
Заголовок раздела «Графики активности»Тайм-серии принимают общие параметры ?bucket=1m|1h|1d&from=&to=; окно
зажимается до глубины хранения.
/series— срабатывания триггерных скриптов (page_open,form_submit,button_click); дляconsole_errorsвернётся пусто./error-series— динамика ошибокconsole_errors./flush-stats— распределение причин отправки батчей (reason → count): видно, какая доля событий уходит черезbeaconпри закрытии страницы.
Безопасность
Заголовок раздела «Безопасность»T<token>подбирается криптостойким случайным генератором (160 бит) — в URL его указывать безопасно ровно настолько, насколько вы сами не выкладываете токен в публично-доступный репозиторий.- Если вы по ошибке закоммитили
T<token>в публичный репозиторий — удалите скрипт через админку илиDELETE /web-script/:id. Старый URL начнёт «глотать» запросы (тихий200 OK) без создания уведомлений. - На каждом успешном триггере поле
lastUsedобновляется — в админке видно, если скрипт «висит» неиспользуемым.