Интеграции

Вебхуки

API

Подключите SeoSync к любому сайту или сервису — получайте данные статей на ваш URL при каждой публикации.

Подключить

Интеграция через вебхуки

Вебхуки позволяют подключить SeoSync к любому сайту или сервису: при каждой публикации статьи данные отправляются POST-запросом на указанный вами URL. Это идеальный вариант для платформ, которые мы не поддерживаем напрямую, автоматизации через Zapier или Make.com, а также для собственных интеграций.

В запросе передаются заголовок, контент (HTML и Markdown), изображения, мета-описание и другие данные.

Частые сценарии:

  • Кастомные CMS и генераторы статических сайтов (Next.js, Hugo, Jekyll)
  • PHP-платформы (PrestaShop, Drupal, кастомные PHP-сайты)
  • Платформы автоматизации (Zapier, Make.com, n8n)
  • Серверлесс-функции (AWS Lambda, Vercel Functions, Cloudflare Workers)
  • Уведомления (Discord, Slack, Email)
  • Любой сервис, принимающий HTTP POST-запросы

Перед началом: вам нужен публично доступный URL-эндпоинт, принимающий POST-запросы с JSON. Это может быть URL вебхука из Zapier, развёрнутая серверлесс-функция или API-роут вашего приложения.

Настройка вебхука

Перейдите в Настройки → Интеграции в вашем дашборде SeoSync и откройте раздел «Вебхуки».

  1. 1

    Введите URL вашего вебхука

    Вставьте публичный URL эндпоинта, который будет принимать данные статей.

  2. 2

    Выберите тип аутентификации

    Подпись (рекомендуется) — криптографическая верификация через HMAC SHA-256. Bearer-токен — простая аутентификация в формате Bearer <token>.

  3. 3

    Подключить и сохранить

    SeoSync сгенерирует уникальный секретный ключ.

  4. 4

    Скопируйте секретный ключ

    Сохраните его в переменных окружения вашего приложения (например, SEOSYNC_WEBHOOK_SECRET).

  5. 5

    Отправьте тестовый запрос

    Нажмите кнопку "Отправить тест", чтобы убедиться, что эндпоинт работает.

Тестирование вебхука

После настройки отправьте тестовый запрос, чтобы убедиться, что всё работает:

  1. 1

    В настройках вебхука найдите раздел «Тестирование».

  2. 2

    Нажмите «Отправить тест» — на ваш эндпоинт отправится пример статьи.

  3. 3

    Проверьте ответ: вы увидите статус-код и ответ вашего эндпоинта.

  4. 4

    Посмотрите логи вашего сервера и убедитесь, что данные получены.

Тестовый запрос содержит поле test: true — вы можете обработать его отдельно в своём коде.

Аутентификация

Каждый запрос содержит заголовки для верификации источника.

Подпись (рекомендуется)

Рекомендуется
  • Заголовок X-SeoSync-Signature с HMAC SHA-256 подписью
  • Заголовок Authorization в формате sha256=<signature>
  • Криптографическая гарантия того, что запрос не был изменён

Bearer-токен

  • Заголовок Authorization в формате Bearer <your-secret-key>
  • Проще в реализации — подходит для Zapier и аналогов

Примеры кода

Node.js — Подпись (рекомендуется)

pages/api/seosync-webhook.js
// /pages/api/seosync-webhook.js
import crypto from 'crypto';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method Not Allowed' });
  }

  const rawBody = await getRawBody(req);
  const secret = process.env.SEOSYNC_WEBHOOK_SECRET;

  // Проверка подписи из заголовка X-SeoSync-Signature
  const signature = req.headers['x-seosync-signature'];
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).json({ message: 'Invalid signature' });
  }

  const article = JSON.parse(rawBody);

  if (article.test) {
    return res.status(200).json({ message: 'Test webhook received successfully' });
  }

  // Здесь ваша логика обработки статьи

  res.status(200).json({ message: 'Webhook received successfully' });
}

function getRawBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => resolve(Buffer.concat(chunks)));
    req.on('error', reject);
  });
}

Node.js — Bearer-токен

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method Not Allowed' });
  }

  const authHeader = req.headers['authorization'];
  const expectedToken = `Bearer ${process.env.SEOSYNC_WEBHOOK_SECRET}`;

  if (!authHeader || authHeader !== expectedToken) {
    return res.status(401).json({ message: 'Invalid authorization' });
  }

  const article = req.body;

  if (article.test) {
    return res.status(200).json({ message: 'Test webhook received successfully' });
  }

  res.status(200).json({ message: 'Webhook received successfully' });
}

PHP — Подпись (рекомендуется)

webhook.php
<?php
$rawBody = file_get_contents('php://input');
$secret = getenv('SEOSYNC_WEBHOOK_SECRET');

$signature = $_SERVER['HTTP_X_SEOSYNC_SIGNATURE'] ?? '';
$expectedSignature = hash_hmac('sha256', $rawBody, $secret);

if ($signature !== $expectedSignature) {
    http_response_code(401);
    echo json_encode(['message' => 'Invalid signature']);
    exit;
}

$article = json_decode($rawBody, true);

if (isset($article['test']) && $article['test'] === true) {
    http_response_code(200);
    echo json_encode(['message' => 'Test webhook received successfully']);
    exit;
}

http_response_code(200);
echo json_encode(['message' => 'Webhook received successfully']);
?>

PHP — Bearer-токен

<?php
$secret = getenv('SEOSYNC_WEBHOOK_SECRET');
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$expectedToken = 'Bearer ' . $secret;

if ($authHeader !== $expectedToken) {
    http_response_code(401);
    echo json_encode(['message' => 'Invalid authorization']);
    exit;
}

$rawBody = file_get_contents('php://input');
$article = json_decode($rawBody, true);

if (isset($article['test']) && $article['test'] === true) {
    http_response_code(200);
    echo json_encode(['message' => 'Test webhook received successfully']);
    exit;
}

http_response_code(200);
echo json_encode(['message' => 'Webhook received successfully']);
?>

Структура payload

Вебхук отправляет данные статьи в формате JSON:

application/json
{
  "title": "Как построить ссылочную массу для SEO",
  "content_html": "<h1>Как построить ссылочную массу...</h1><p>...</p>",
  "content_markdown": "# Как построить ссылочную массу...\n\n...",
  "slug": "kak-postroit-ssylky-dlya-seo",
  "meta_description": "Проверенные стратегии наращивания бэклинков...",
  "status": "published",
  "featured_image": "https://images.unsplash.com/photo-1516321318423-f06f85e504b3",
  "published_url": "https://yourblog.com/blog/kak-postroit-ssylky-dlya-seo",
  "scheduled_date": null,
  "published_at": "2024-03-15T10:30:00Z",
  "is_republish": false,
  "test": false
}
ПолеОписание
titleЗаголовок статьи
content_htmlПолный текст статьи в формате HTML
content_markdownПолный текст статьи в формате Markdown
slugURL-slug статьи (не изменяется при обновлениях)
meta_descriptionSEO-описание
statusВсегда "published" при срабатывании вебхука
featured_imageURL главного изображения (если есть)
published_urlURL, по которому опубликована статья (если доступен)
published_atВременная метка отправки вебхука
is_republishtrue при обновлении существующей статьи, false при первой публикации
testtrue для тестовых запросов через кнопку "Отправить тест"

Обновление статей

После публикации кнопка «Обновить статью» отправит обновлённые данные на ваш вебхук с флагом is_republish: true. Используйте upsert, чтобы избежать дубликатов:

// Supabase
await supabase
  .from('articles')
  .upsert({
    slug: article.slug,
    title: article.title,
    content_html: article.content_html,
  }, { onConflict: 'slug' });

// MongoDB
await collection.updateOne(
  { slug: article.slug },
  { $set: articleData },
  { upsert: true }
);

// Prisma
await prisma.article.upsert({
  where: { slug: article.slug },
  update: articleData,
  create: articleData
});

Важно

  • Поле slug не изменяется при обновлениях — используйте его как уникальный идентификатор
  • Флаг is_republish помогает отличить обновление от первой публикации
  • Всегда обрабатывайте оба случая, чтобы исключить дубликаты

Другие интеграции

Настройте SeoSync на другой платформе