Как это работает
После генерации статьи SeoSmith отправляет POST-запрос на URL вашего сайта. В теле запроса — готовая статья: HTML-текст, мета-теги, FAQ, JSON-LD разметка. Ваш сервер проверяет подпись, сохраняет статью и возвращает {"ok": true}.
Webhook-коннектор работает без участия пользователя. Как только статья сгенерирована — она уже летит на ваш сайт. Кнопка «Опубликовать» не нужна.
Что приходит в запросе
SeoSmith отправляет POST-запрос с заголовками:
Content-Type: application/json X-SeoSmith-Signature: sha256=<hmac-hex> X-SeoSmith-Event: article.published
Тело запроса — JSON следующей структуры:
{
"event": "article.published",
"article": {
"id": 123,
"title": "Заголовок статьи (H1)",
"slug": "slug-stati",
"url": "slug-stati",
"body": "<p>Готовый HTML-текст статьи...</p>",
"meta": {
"title": "SEO-заголовок до 60 символов",
"description": "Мета-описание до 165 символов",
"keywords": ["ключевое слово 1", "ключевое слово 2"]
},
"faq": [
{ "q": "Вопрос?", "a": "Ответ." }
],
"json_ld": "{...BlogPosting schema...}",
"image_prompt": "Описание для генерации изображения",
"alt_text": "Alt-текст для изображения"
},
"company": {
"name": "Название компании",
"site_url": "https://ваш-сайт.ru"
}
}
| Поле | Описание |
|---|---|
article.body | Готовый HTML — вставляйте в <article> напрямую |
article.meta.title | Для тега <title> и og:title |
article.meta.description | Для <meta name="description"> |
article.slug | URL-путь статьи, например kak-uluchshit-seo |
article.json_ld | BlogPosting Schema.org — вставляйте в <head> |
article.faq | Массив вопрос-ответ для FAQPage разметки |
Шаг 1: Создать эндпоинт
Добавьте в свой проект POST-роут. Логика одинакова для любого стека:
- Прочитать тело запроса как сырой текст (до парсинга JSON)
- Вычислить HMAC-SHA256 от тела с ключом
SEOSMITH_WEBHOOK_SECRET - Сравнить с подписью из заголовка
X-SeoSmith-Signature(без префиксаsha256=) - Если не совпадает — вернуть 401
- Распарсить JSON и сохранить статью
- Вернуть
{"ok": true}со статусом 200
Примеры реализации:
Next.js (App Router)
// app/api/seosmith/route.ts
import { createHmac, timingSafeEqual } from "crypto";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const rawBody = await req.text();
const signature = req.headers.get("x-seosmith-signature") ?? "";
const received = signature.replace("sha256=", "");
const expected = createHmac("sha256", process.env.SEOSMITH_WEBHOOK_SECRET!)
.update(rawBody)
.digest("hex");
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const payload = JSON.parse(rawBody);
if (payload.event !== "article.published") {
return NextResponse.json({ ok: true, skipped: true });
}
const { article } = payload;
// Сохраните статью в вашу БД или файловую систему
// await db.articles.create({ slug: article.slug, body: article.body, ... })
return NextResponse.json({ ok: true });
}
Laravel
// routes/api.php
Route::post('/seosmith', function (Request $request) {
$rawBody = $request->getContent();
$signature = $request->header('X-SeoSmith-Signature', '');
$received = str_replace('sha256=', '', $signature);
$expected = hash_hmac('sha256', $rawBody, env('SEOSMITH_WEBHOOK_SECRET'));
if (!hash_equals($expected, $received)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
$payload = $request->json()->all();
if (($payload['event'] ?? '') !== 'article.published') {
return response()->json(['ok' => true, 'skipped' => true]);
}
$article = $payload['article'];
// Article::create(['slug' => $article['slug'], 'body' => $article['body'], ...]);
return response()->json(['ok' => true]);
});
Python (FastAPI)
import hashlib, hmac, os
from fastapi import APIRouter, Header, HTTPException, Request
from fastapi.responses import JSONResponse
router = APIRouter()
@router.post("/api/seosmith")
async def receive_webhook(
request: Request,
x_seosmith_signature: str = Header(alias="X-SeoSmith-Signature"),
):
raw_body = await request.body()
secret = os.environ["SEOSMITH_WEBHOOK_SECRET"]
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
received = x_seosmith_signature.removeprefix("sha256=")
if not hmac.compare_digest(expected, received):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = await request.json()
if payload.get("event") != "article.published":
return JSONResponse({"ok": True, "skipped": True})
article = payload["article"]
# Сохраните статью в вашу БД
return JSONResponse({"ok": True})
HMAC вычисляется от сырого тела запроса, а не от объекта JSON. Если сначала распарсить, а потом сериализовать обратно — порядок ключей может измениться и подпись не совпадёт.
Куда сохранять статью
Это зависит от архитектуры вашего проекта. Универсального ответа нет — выберите подходящий вариант:
| Тип проекта | Куда сохранять | URL статьи |
|---|---|---|
| Next.js / Nuxt с БД | Таблица articles или posts в вашей БД |
/blog/[slug] — динамический роут |
| Next.js / Nuxt с MDX | Файл /content/blog/{slug}.mdx |
/blog/[slug] — файловый роутинг |
| Laravel / Django | Таблица posts или articles |
/blog/{slug} — роут в контроллере |
| Статический сайт (Hugo, Jekyll) | Файл /content/posts/{slug}.md |
/posts/{slug}/ — после пересборки |
| Headless CMS (Strapi, Directus) | API CMS — POST /api/articles | Зависит от настроек CMS |
Поле article.slug — уникальный URL-совместимый идентификатор статьи на кириллице, транслитерированный в латиницу. Используйте его как первичный ключ или имя файла. Путь до блога (/blog/, /articles/) добавляйте сами — вы знаете структуру своего сайта лучше.
Если в проекте уже есть таблица или папка для публичных страниц — используйте её. Если структура ещё не определена, создайте новую: например таблицу seo_articles со столбцами slug, title, body, meta_title, meta_description, created_at.
Шаг 2: Задеплоить изменения
Опубликуйте код на сервер. Эндпоинт должен быть доступен по HTTPS — SeoSmith не принимает HTTP-адреса.
Проверьте что эндпоинт отвечает (должен вернуть 401 — подпись не передана, но эндпоинт живой):
curl -X POST https://ваш-сайт.ru/api/seosmith
Шаг 3: Сохранить коннектор в SeoSmith и получить секрет
- Откройте SeoSmith → нужный проект → Интеграции
- Выберите карточку Webhook — свой сайт или Вайбкод
- Вставьте URL эндпоинта:
https://ваш-сайт.ru/api/seosmith - Нажмите Сохранить
- Появится Шаг 3 с секретом — скопируйте его сразу, он показывается только один раз
После закрытия секрет нельзя восстановить. Если потеряли — удалите коннектор и создайте заново, получите новый секрет.
Шаг 4: Добавить секрет в .env
Добавьте секрет в файл .env вашего проекта — и локально, и на сервере:
SEOSMITH_WEBHOOK_SECRET=вставьте-секрет-здесь
После добавления на сервере обязательно перезапустите приложение — переменные окружения читаются только при старте:
| Стек | Команда перезапуска |
|---|---|
| Docker Compose | docker compose up -d backend |
| PM2 (Node.js) | pm2 restart all |
| Systemd | sudo systemctl restart your-app |
| Vercel / Railway | Добавить через панель Environment Variables — деплой автоматический |
| Heroku | heroku config:set SEOSMITH_WEBHOOK_SECRET=значение |
Шаг 5: Проверить подключение
Нажмите «Проверить» в карточке коннектора. SeoSmith отправит тестовый запрос с событием test — ваш эндпоинт должен вернуть 200.
Если всё прошло успешно — статус сменится на «Подключено». Теперь каждая новая статья будет автоматически отправляться на ваш сайт.
Решение типичных проблем
401 — Invalid signature
Значения секрета в SeoSmith и в .env на сервере не совпадают, или приложение не было перезапущено после добавления секрета. Проверьте оба варианта.
500 — Webhook secret not configured
Переменная SEOSMITH_WEBHOOK_SECRET не добавлена в .env на сервере, или приложение не было перезапущено. Добавьте переменную и перезапустите.
Тест проходит, но статьи не приходят
Тестовый запрос содержит event: "test". Убедитесь, что ваш эндпоинт обрабатывает event: "article.published" и не игнорирует его. Проверьте логи приложения в момент генерации статьи.
Эндпоинт недоступен по HTTPS
SeoSmith принимает только HTTPS-адреса. Если у вас нет SSL-сертификата — получите бесплатный через Let's Encrypt (certbot) или разверните проект на платформе с автоматическим SSL (Vercel, Railway, Render).