Webhooki
Automatycznie wysyłaj wyniki pod własny adres HTTP.
Skonfigurujesz webhook na kanale Adres HTTP, przetestujesz go bez ryzyka i przygotujesz połączony system na ponowne wysłanie tego samego wyniku.
Co robi webhook
Webhook wysyła wynik z SemanticHuba pod wskazany adres internetowy. Dzięki temu system redakcyjny, własna aplikacja albo narzędzie takie jak Zapier, Make czy n8n mogą automatycznie odebrać gotowe dane.
Do konfiguracji zwykle potrzebujesz pomocy osoby, która utrzymuje stronę lub aplikację odbierającą wyniki. Przekaż jej adres tej strony dokumentacji oraz przykładowe dane poniżej.
Włącz go bezpiecznie
- W kroku Akcja wybierz kafelek Adres HTTP.
- W polu Tryb wysyłki zostaw Testowy.
- Kliknij Wyślij test. Jedno przykładowe zdarzenie pójdzie tą samą drogą co prawdziwa dostawa i od razu zobaczysz odpowiedź odbiorcy. Działa też na niezapisanym adresie, więc literówkę wychwycisz przed zapisem.
- Przygotuj aplikację tak, aby nie tworzyła duplikatu po ponownym odebraniu tego samego wyniku.
- Wpisz prawdziwy Adres URL odbiorcy i opcjonalny Sekret.
- Przestaw Tryb wysyłki na Produkcja.
- Wyślij jeden zaakceptowany wynik i sprawdź go w aplikacji docelowej.
- Dopiero potem wybierz Wyślij od razu.
Sekret
Sekret jest opcjonalny. Po zapisie SemanticHub go nie pokazuje: widzisz tylko informację Sekret ustawiony z datą oraz przyciski Zmień i Usuń.
Przy każdej wysyłce ten sam sekret służy do dwóch rzeczy:
- idzie w nagłówku
Authorization: Bearer …; - jest kluczem podpisu HMAC w nagłówku
X-SH-Signature.
Odbiorca sprawdza ten sposób, który obsługuje. Bez sekretu żądanie idzie bez obu nagłówków.
Ponawianie wysyłki
Jedna dostawa to najwyżej trzy próby: pierwsza, a po niepowodzeniu jeszcze dwie, po około 1 minucie i po około 5 minutach. Jedna próba może trwać najwyżej 30 sekund.
Ponowienie następuje po problemie z siecią, przekroczeniu czasu, odpowiedzi 429 lub błędzie serwera 5xx. Pozostałe błędy 4xx zwykle wymagają poprawienia adresu, danych albo uwierzytelnienia i nie są ponawiane. Po trzeciej nieudanej próbie dostawa jest oznaczana jako nieudana i możesz ponowić ją ręcznie z panelu wyniku.
Uzgadnianie możliwości odbiorcy
Odbiorca może zgłosić SemanticHubowi, co potrafi złożyć: jakie rodzaje sekcji i elementów treści renderuje. Robi to na przykład wtyczka WordPress, ale własna aplikacja też może się w ten sposób przedstawić.
- Pierwsze zgłoszenie na celu, który nie ma jeszcze ustalonego kontraktu treści, ustawia ten kontrakt na podstawie możliwości odbiorcy. Nie musisz go konfigurować ręcznie.
- Kolejne zgłoszenia tylko zapisują nowe możliwości. Zgłoszenie nigdy nie wstrzymuje dostawy.
- Sekcja, której odbiorca nie renderuje sam, idzie w formie zapasowej jako zwykłe bloki treści.
- Raz w tygodniu dostajesz e-mail, jeśli ręcznie ustawiony kontrakt rozjechał się z możliwościami odbiorcy albo wtyczka przestała się zgłaszać.
Informacje dla programisty
SemanticHub wysyła żądanie HTTP POST z danymi w formacie JSON. Każdy status 2xx potwierdza poprawne odebranie wyniku.
Nagłówki
Content-Type: application/jsonIdempotency-Key: stały identyfikator danego wynikuX-SH-Delivery: identyfikator serii prób; ponowienie tej samej treści niesie ten sam identyfikatorX-SH-Timestamp: czas wysłania w sekundach od 1970 rokuAuthorization: Bearer …iX-SH-Signature: sha256=…, jeśli ustawisz sekret
Podpis to HMAC-SHA256(sekret, X-SH-Timestamp + "." + surowe body) zapisany szesnastkowo. Licz go z bajtów żądania, zanim zamienisz je na obiekt JSON.
Prosty przykład odbioru
import express from 'express'
const app = express()
app.use(express.json())
app.post('/semantic-hub', async (req, res) => {
const deliveryId = req.header('X-SH-Delivery')
// Zapisz identyfikator razem z danymi. Jeśli identyfikator już istnieje,
// nie twórz drugiej publikacji.
await enqueueOnce(deliveryId, req.body)
res.status(202).end()
})Najważniejsze przesyłane pola
Każdy odbiorca dostaje ten sam kształt danych, payload_version: 5. Nie wybierasz wersji i nie ustawiasz własnego szablonu JSON.
{
"payload_version": 5,
"mode": "article",
"event": "workflow.action",
"result_id": "id-wyniku",
"revision": 1,
"workflow_execution": "id-wyniku",
"workflow_name": "Nazwa celu",
"title": "Tytuł wyniku",
"lead": "Zajawka do zapowiedzi i opisu meta.",
"executed_at": "2026-09-04T10:00:00+00:00",
"published_at": "2026-09-04T10:00:00+00:00",
"source_lang": "pl",
"articles_count": 3,
"articles": [],
"clusters": [],
"llm_response": "## Nagłówek\n\nTreść w markdownie…",
"locales": {
"pl": {
"title": "Tytuł wyniku",
"slug": "tytul-wyniku",
"lead": "Zajawka…",
"body": "## Nagłówek\n\nTreść w markdownie…",
"seo_description": "Zajawka…",
"body_html": "<h2>Nagłówek</h2><p>Treść w markdownie…</p>"
}
},
"steps": {
"Redaktor": { "text": "…", "model": "model-id", "tokens": 900, "cost": 0.0004 }
},
"ai_model": "model-id",
"tokens_used": 1234,
"cost": 0.01
}- mode:
article, gdy wynik ma wygenerowaną treść, albocluster, gdy wysyłamy sam klaster wraz z artykułami źródłowymi. W trybie klastrowymllm_responsejest pusty (null), nie malocales, astepsto pusty obiekt. - result_id i revision:
result_idnie zmienia się między kolejnymi wersjami tego samego wyniku, arevisionrośnie przy każdej zmianie treści. Po tej parze odbiorca rozpoznaje, który wpis zaktualizować. - executed_at i published_at: czas powstania wyniku. Ponowienie wysyła te same wartości.
- llm_response: treść w formacie markdown (nagłówki
##, tabele GFM, cytaty). Kanoniczna struktura podróżuje wsections. - locales: gotowa wersja wyniku w języku źródłowym, z adresem
slugi treścią już zamienioną na HTML. Drugi język pojawia się tylko wtedy, gdy włączysz Wersja w drugim języku. - steps: wyjście każdego kroku workflow pod jego nazwą. Odbiorca bierze stąd to, czego potrzebuje, na przykład sam brief albo wersję przed redakcją.
- title i lead: zawsze obecne, choć mogą być puste (
null).leadto zajawka z edytora. Wpisz ją w zapowiedź lub opis meta zamiast wycinać pierwszy akapit treści. Opis wclusters[0].descriptiondotyczy klastra i nie jest tym samym tekstem.
Część pól pojawia się tylko wtedy, gdy jest co wysłać. Brak klucza nie jest błędem i nie znaczy „wartość pusta”:
{
"sections": [{ "key": "body", "blocks": [] }],
"image": { "url": "https://…", "attribution": "…" },
"tags": ["słowo kluczowe"],
"publish_mode": "draft",
"fields": { "category": "zdrowie" }
}sections: pomijane, gdy wynik nie ma sekcji (nie wysyłamy"sections": []);image: tylko gdy ktoś faktycznie wybrał obrazek wyróżniający; klucz niesie też atrybucję;tags: tylko gdy klaster ma słowa kluczowe;publish_mode: tylko gdy cel ustawił politykę publikacji (moderation,draftlubpublish). To prośba, nie rozkaz: odbiorca i tak sprawdza własne uprawnienia.fields: tylko gdy odbiorca zgłosił pola, których wymaga (opis w sekcji Pola odbiorcy).
Aplikacja odbierająca dane powinna pomijać pola, których nie zna, zamiast odrzucać całe żądanie.
Kolejna wersja wyniku
Gdy do opublikowanego już klastra dojdą nowe artykuły albo gdy zmienisz treść wysłanego wyniku i wyślesz go ponownie, kolejna wersja idzie jako event: "cluster.updated" z wyższym revision. Taka dostawa dokłada dwa klucze, których nie ma w pierwszej wysyłce:
{
"event": "cluster.updated",
"revision": 2,
"parent_execution": "id-poprzedniego-wyniku",
"new_articles": ["id-artykulu"]
}Po zmianie samej treści wyniku, który nie ma poprzedniej wersji, parent_execution jest null, a new_articles puste. Kolejna wersja klastra z nowymi artykułami niesie parent_execution. Jeśli Twój system zna już wyższą wersję, odpowiedz 409 z polem revision w body. Wyślemy wtedy wynik ponownie z kolejnym numerem.
Pola odbiorcy
Twój system może wymagać przy każdym wpisie wartości, których treść wyniku nie niesie, na przykład kategorii albo działu. Zgłasza je w tym samym meldunku możliwości co bloki i sekcje: PUT /api/goals/{id}/target-manifest z tokenem API. Poniżej fragment takiego meldunku:
{
"source": "receiver",
"fields": [
{
"key": "category",
"label": "Kategoria",
"type": "enum",
"required": true,
"multiple": false,
"options": [{ "value": "zdrowie", "label": "Zdrowie" }]
}
]
}sourcemusi mieć wartość"receiver". Meldunek bez niej traktujemy jak meldunek wtyczki WordPress, a jego pola nie obowiązują celu z kanałem Adres HTTP.keyto małe litery, cyfry i_, najwyżej 40 znaków.typetoenum,textalbobool. Poleenummusi mieć listęoptions, amultiplejest dozwolone tylko dlaenum.- Wartości ustawiasz w SemanticHubie: domyślne w kroku Akcja, w sekcji Pola odbiorcy, a dla pojedynczego wyniku w zakładce Dostawa. Wartość spoza zgłoszonej listy zostanie odrzucona przy zapisie.
- Payload niesie klucz
fieldsze słownikiemklucz: wartość. Bez zgłoszonych pól tego klucza nie ma. - Wynik bez wartości pola wymaganego nie zostanie wysłany. Wraca w SemanticHubie do statusu Do akceptacji i czeka na uzupełnienie.
Inny kształt danych
Kształtu JSON nie zmienisz po stronie SemanticHuba. Jeśli Twój system oczekuje innych pól, wklej jako adres URL adres z Zapiera, Make albo n8n i tam przepisz dane na potrzebny kształt.
Wymagania dotyczące adresu
Adres webhooka musi być dostępny z publicznego internetu. Ze względów bezpieczeństwa SemanticHub blokuje adresy prowadzące do urządzenia lokalnego, sieci prywatnej i wewnętrznych usług chmurowych.