Webhooks

При разработке картографических приложений на базе Mapbox GL JS часто возникает необходимость реагировать на события, происходящие вне браузера. Например:

  • завершение обработки пространственных данных;
  • публикация новых тайлов;
  • изменение состояния трекинга транспорта;
  • обновление геоданных в базе;
  • завершение импорта GeoJSON-файлов;
  • получение уведомлений от внешних сервисов.

Для решения подобных задач используются Webhooks — механизм автоматической доставки HTTP-запросов между системами при наступлении определённого события.

Mapbox GL JS сам по себе является клиентской библиотекой и не предоставляет встроенной системы вебхуков. Однако вебхуки широко используются совместно с Mapbox-приложениями через серверную инфраструктуру.

Типичная схема выглядит следующим образом:

Источник данных
       │
       ▼
    Webhook
       │
       ▼
 Backend API
       │
       ▼
 База данных
       │
       ▼
 Mapbox GL JS

После получения вебхука сервер обновляет данные, а клиентское приложение отображает изменения на карте.


Что такое Webhook

Webhook представляет собой HTTP-запрос, автоматически отправляемый одной системой в другую после возникновения определённого события.

В отличие от регулярного опроса сервера (Polling), вебхуки работают по модели уведомлений.

Polling

Клиент → Сервер
Есть изменения?

Клиент → Сервер
Есть изменения?

Клиент → Сервер
Есть изменения?

Webhook

Событие произошло
        │
        ▼
Сервер автоматически отправляет запрос
        │
        ▼
Получатель обновляет данные

Такой подход:

  • уменьшает нагрузку на сервер;
  • сокращает сетевой трафик;
  • обеспечивает более быструю реакцию на изменения;
  • позволяет строить событийную архитектуру.

Роль Webhooks в картографических приложениях

Для Mapbox GL JS вебхуки обычно выступают связующим звеном между источником данных и интерфейсом карты.

Пример процесса:

  1. Пользователь загружает GPS-трек.
  2. Сервер начинает обработку.
  3. После завершения обработки генерируется событие.
  4. Отправляется Webhook.
  5. Сервер обновляет GeoJSON.
  6. Карта получает новые данные.
  7. На карте появляется новый маршрут.

С точки зрения Mapbox GL JS изменения становятся видимыми через обновление источников данных.


Архитектура взаимодействия

Рассмотрим распространённую архитектуру.

               +----------------+
               | External System|
               +----------------+
                        |
                    Webhook
                        |
                        ▼
               +----------------+
               | Backend Server |
               +----------------+
                        |
                Save GeoJSON
                        |
                        ▼
               +----------------+
               | Database       |
               +----------------+
                        |
                  REST API
                        |
                        ▼
               +----------------+
               | Mapbox GL JS   |
               +----------------+

Mapbox GL JS не принимает вебхуки напрямую.

Причины:

  • браузер не может выступать постоянной точкой приёма HTTP-событий;
  • пользователь может закрыть вкладку;
  • адрес браузера недоступен извне;
  • необходимо выполнять проверку безопасности.

Поэтому вебхуки всегда принимаются серверной частью приложения.


Создание обработчика Webhook на Node.js

Наиболее распространённый вариант — использование Express.

Установка

npm install express

Создание сервера

const express = require('express');

const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {

    console.log('Получен webhook');

    console.log(req.body);

    res.status(200).send('OK');

});

app.listen(3000, () => {
    console.log('Server started');
});

После запуска сервер начинает принимать POST-запросы по адресу:

http://localhost:3000/webhook

Пример структуры Webhook

Типичный JSON может выглядеть следующим образом:

{
  "event": "vehicle.updated",
  "vehicleId": 42,
  "latitude": 51.1694,
  "longitude": 71.4491,
  "timestamp": "2025-04-15T10:25:00Z"
}

После получения такого события можно обновить координаты объекта в базе данных.


Обновление данных карты после получения Webhook

Предположим, сервер сохраняет последние координаты транспорта.

Клиентское приложение запрашивает данные через API.

Получение данных

async function loadVehicles() {

    const response = await fetch('/api/vehicles');

    const geojson = await response.json();

    map.getSource('vehicles').setData(geojson);

}

Периодическое обновление

setInterval(loadVehicles, 5000);

Теперь:

  1. Webhook обновляет БД.
  2. Клиент получает новые данные.
  3. Mapbox GL JS перерисовывает объекты.

Использование WebSocket после Webhook

Часто применяется более эффективная схема.

Webhook
    │
    ▼
Backend
    │
    ▼
WebSocket
    │
    ▼
Mapbox GL JS

После получения вебхука сервер отправляет сообщение через WebSocket всем подключённым клиентам.

Сервер

io.emit('vehicle-update', data);

Клиент

socket.on('vehicle-update', geojson => {

    map.getSource('vehicles').setData(geojson);

});

Преимущество такого подхода — обновления появляются практически мгновенно.


Работа с событиями геолокации

Одним из самых распространённых сценариев является отслеживание транспорта.

Webhook:

{
  "vehicleId": 105,
  "lat": 43.2389,
  "lng": 76.8897
}

Обработчик:

app.post('/webhook', async (req, res) => {

    const { vehicleId, lat, lng } = req.body;

    await db.updateVehicle(vehicleId, lat, lng);

    res.sendStatus(200);

});

После обновления БД новые координаты становятся доступны карте.


Обновление GeoJSON-файлов

Многие приложения используют GeoJSON как основной источник пространственных данных.

Webhook может инициировать обновление файла.

app.post('/webhook', async (req, res) => {

    await generateGeoJSON();

    res.sendStatus(200);

});

Генерация:

async function generateGeoJSON() {

    const features = await getFeatures();

    const geojson = {
        type: 'FeatureCollection',
        features
    };

    await fs.writeFile(
        'data.geojson',
        JSON.stringify(geojson)
    );

}

Mapbox GL JS затем загружает обновлённый источник.


Автоматическое обновление источника данных

Первоначальное подключение источника:

map.addSource('cities', {
    type: 'geojson',
    data: '/data.geojson'
});

Последующее обновление:

async function refreshData() {

    const response =
        await fetch('/data.geojson');

    const data =
        await response.json();

    map.getSource('cities')
       .setData(data);

}

Проверка подлинности Webhook

Никогда не следует доверять входящим запросам без проверки.

Большинство поставщиков вебхуков используют подписи.

Пример заголовка:

X-Signature: abc123456789

Проверка:

const crypto = require('crypto');

function verifySignature(
    payload,
    signature,
    secret
) {

    const hash =
        crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');

    return hash === signature;
}

Использование:

app.post('/webhook', (req, res) => {

    const signature =
        req.headers['x-signature'];

    if (!verifySignature(
        JSON.stringify(req.body),
        signature,
        process.env.SECRET
    )) {

        return res.sendStatus(401);
    }

    res.sendStatus(200);

});

Защита от повторной доставки

Многие сервисы могут повторно отправлять webhook.

Например:

{
  "eventId": "evt_123456",
  "event": "route.updated"
}

Перед обработкой необходимо проверять уникальность идентификатора события.

const exists =
    await db.hasEvent(eventId);

if (exists) {
    return;
}

После обработки:

await db.saveEvent(eventId);

Такой подход называется Idempotency.


Очереди обработки Webhooks

Если поступает большое количество уведомлений, непосредственная обработка может привести к перегрузке сервера.

Используется очередь сообщений.

Webhook
    │
    ▼
 Message Queue
    │
    ▼
 Worker
    │
    ▼
 Database

Популярные решения:

  • RabbitMQ
  • Apache Kafka
  • Redis (через очереди)

Пример добавления задания:

await queue.add({
    event: req.body
});

Вебхуки для обновления слоёв карты

Предположим, поступает информация о новой зоне обслуживания.

Webhook:

{
  "event": "zone.created",
  "zoneId": 12
}

После получения:

await rebuildZonesGeoJSON();

Клиент обновляет слой:

map.getSource('zones')
   .setData(newData);

Обновление происходит без перезагрузки страницы.


Использование Webhooks в системах мониторинга

В задачах мониторинга вебхуки часто применяются для передачи событий:

  • аварии оборудования;
  • превышение скорости;
  • выход объекта из геозоны;
  • потеря GPS-сигнала;
  • изменение статуса устройства.

Пример:

{
  "event": "geofence.exit",
  "assetId": 87,
  "geofenceId": 15
}

После получения сервер может:

  • обновить базу данных;
  • отправить уведомление;
  • изменить отображение объекта на карте.

Интеграция с внешними платформами

В картографических системах вебхуки часто поступают от:

  • CRM-систем;
  • ERP-систем;
  • IoT-платформ;
  • систем мониторинга транспорта;
  • облачных сервисов аналитики;
  • платформ доставки.

Пример интеграции:

IoT Device
     │
     ▼
Cloud Platform
     │
     ▼
Webhook
     │
     ▼
Backend
     │
     ▼
Mapbox GL JS

Такая архитектура позволяет визуализировать изменения практически в режиме реального времени.


Логирование Webhooks

Для диагностики рекомендуется сохранять все входящие события.

Пример:

app.post('/webhook', async (req, res) => {

    await db.saveLog({
        payload: req.body,
        createdAt: new Date()
    });

    res.sendStatus(200);

});

Логи помогают:

  • искать ошибки интеграции;
  • анализировать потерянные события;
  • выполнять повторную обработку;
  • проводить аудит системы.

Обработка ошибок

Надёжный обработчик вебхуков всегда содержит механизм обработки исключений.

app.post('/webhook', async (req, res) => {

    try {

        await processWebhook(req.body);

        res.sendStatus(200);

    } catch (error) {

        console.error(error);

        res.sendStatus(500);

    }

});

Большинство поставщиков вебхуков автоматически повторяют отправку при получении кодов:

500
502
503
504

Поэтому важно корректно различать успешную и неуспешную обработку.


Версионирование Webhooks

При развитии API структура событий может изменяться.

Пример:

{
  "version": "2.0",
  "event": "asset.updated"
}

Обработчик:

switch(req.body.version) {

    case '1.0':
        processV1(req.body);
        break;

    case '2.0':
        processV2(req.body);
        break;

}

Версионирование позволяет обновлять систему без нарушения совместимости.


Рекомендации по проектированию

Сервер должен отвечать максимально быстро

res.sendStatus(200);

Тяжёлая обработка переносится в фоновые задачи.

Необходимо проверять подписи запросов

Любой внешний запрос считается недоверенным до прохождения проверки.

Следует использовать идемпотентную обработку

Повторная доставка не должна приводить к дублированию данных.

Желательно применять очереди сообщений

Особенно при обработке большого потока пространственных событий.

Необходимо вести журнал событий

Это значительно упрощает поддержку и отладку картографических систем.

Обновление карты должно быть отделено от обработки Webhook

Webhook изменяет данные на сервере, а Mapbox GL JS отвечает только за визуализацию обновлённого состояния системы.