Вебхуки
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
Введите URL вашего вебхука
Вставьте публичный URL эндпоинта, который будет принимать данные статей.
- 2
Выберите тип аутентификации
Подпись (рекомендуется) — криптографическая верификация через HMAC SHA-256. Bearer-токен — простая аутентификация в формате Bearer <token>.
- 3
Подключить и сохранить
SeoSync сгенерирует уникальный секретный ключ.
- 4
Скопируйте секретный ключ
Сохраните его в переменных окружения вашего приложения (например, SEOSYNC_WEBHOOK_SECRET).
- 5
Отправьте тестовый запрос
Нажмите кнопку "Отправить тест", чтобы убедиться, что эндпоинт работает.
Тестирование вебхука
После настройки отправьте тестовый запрос, чтобы убедиться, что всё работает:
- 1
В настройках вебхука найдите раздел «Тестирование».
- 2
Нажмите «Отправить тест» — на ваш эндпоинт отправится пример статьи.
- 3
Проверьте ответ: вы увидите статус-код и ответ вашего эндпоинта.
- 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
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 — Подпись (рекомендуется)
<?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:
{
"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 |
slug | URL-slug статьи (не изменяется при обновлениях) |
meta_description | SEO-описание |
status | Всегда "published" при срабатывании вебхука |
featured_image | URL главного изображения (если есть) |
published_url | URL, по которому опубликована статья (если доступен) |
published_at | Временная метка отправки вебхука |
is_republish | true при обновлении существующей статьи, false при первой публикации |
test | true для тестовых запросов через кнопку "Отправить тест" |
Обновление статей
После публикации кнопка «Обновить статью» отправит обновлённые данные на ваш вебхук с флагом 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 на другой платформе