Poradnik połączenia

Podłącz własny endpoint

Specyfikacja integracji dla osoby, która utrzymuje stronę: jedno żądanie POST, podpisany JSON i dokładnie to, czego oczekujemy w odpowiedzi.

To połączenie jest inne niż pozostałe. Przy WordPressie czy Joomli to my uczymy się cudzego API. Tutaj API jesteś Ty: podajesz nam adres, a my dostarczamy pod niego każdy artykuł jako jedno podpisane żądanie HTTP. Odbiornikiem może być kontroler w Twojej aplikacji, funkcja bezserwerowa, scenariusz w Make albo webhook w n8n.

Ta strona nie jest instrukcją klikania, tylko specyfikacją. Poniżej znajdziesz dokładny kształt żądania, każde pole treści z typem, schemat podpisu na tyle dokładny, żeby dało się go zweryfikować, oraz to, co robimy z każdą możliwą odpowiedzią. Jeśli utrzymujesz stronę klienta, to wszystko, czego potrzebujesz.

Nazwy pól i kluczy JSON podajemy po angielsku, bo tak wyglądają na łączu i tak trzeba je wpisać w kodzie. Tłumaczenie ich w tekście wysłałoby Cię na poszukiwanie klucza, którego w treści żądania nie ma.

Czym jest to połączenie

Jedno żądanie na artykuł. Nic nie odpytuje Twojej strony w pętli, nic nie utrzymuje otwartego połączenia i nie ma żadnego ruchu w drugą stronę. Kiedy artykuł jest gotowy, wysyłamy go pod Twój adres i czekamy na odpowiedź.

Kiedy warto sięgnąć po to połączenie

  • Twoja strona to własna aplikacja, a Ty wolisz przyjąć artykuł u siebie i sam zdecydować, jak go zapisać i wyrenderować.
  • Twoja platforma nie ma żadnego użytecznego API bloga, więc nie ma czego podłączyć w normalny sposób.
  • Wystarczy Ci mała funkcja pośrednicząca, która przyjmie artykuł i przełoży go na cokolwiek, czego naprawdę używasz.
  • Pracujesz w narzędziu bez kodu, które potrafi przyjąć webhook, i chcesz poprowadzić artykuł dalej własnym przepływem.

Dwie rzeczy, których to połączenie nie robi i nie będzie robić w tej wersji. Nie czyta katalogu produktów, bo za webhookiem nie ma katalogu do przeczytania. Nie wysyła też szkiców: szkic to obietnica, że nikt nie zobaczy tekstu przed akceptacją, a takiej obietnicy nie możemy złożyć za cudzy serwer, więc pole statusu zawsze mówi publish.

Twój odbiornik jest jedynym miejscem, w którym artykuł ląduje. Nie mamy do Twojej strony żadnego innego dostępu, nie logujemy się nigdzie i nie mamy Twoich haseł. Cały kontakt to opisane niżej żądanie.

1. Adres, który nam podajesz

Podajesz nam dwie wartości i nic poza tym. Obie trzymamy zaszyfrowane i żadna z nich nigdy nie pojawia się w treści żądania, w nagłówku ani w logu.

Dwie wartości

  • Adres endpointu. Pełny adres, pod który mamy wysyłać. Musi zaczynać się od https://. Wysyłamy dokładnie pod ten adres, znak w znak: nie doklejamy do niego żadnej ścieżki ani parametru.
  • Sekret do podpisu. Wspólny sekret, osobny dla tego połączenia. Wygeneruj co najmniej 32 losowe bajty. Nigdy go nie wysyłamy, używamy go wyłącznie do policzenia podpisu, a Ty tym samym sekretem podpis sprawdzasz.

Adres endpointu i sekret ustawiamy razem z Tobą przy starcie. Przesyłasz je nam bezpiecznie, a my podpinamy je do Twojej witryny i wykonujemy próbne wywołanie, żebyś zobaczył je u siebie, zanim ruszy harmonogram. Napisz na kontakt@agentshub.pl.

Sekret możesz podać własny albo poprosić, żebyśmy wygenerowali go po naszej stronie. W obu przypadkach obie strony trzymają tę samą wartość i tylko ona pozwala zweryfikować podpis. Jeśli kiedykolwiek wycieknie, napisz do nas, a wymienimy go od ręki.

Co ma być widoczne: Ekran naszego panelu z formularzem połączenia dla własnego endpointu, z widocznym polem adresu i polem sekretu, oraz przyciskiem testu połączenia.

Co sprawdzamy w adresie przed każdym żądaniem

Adres jest sprawdzany na nowo przed każdą dostawą, łącznie z testowym pingiem. Adres, który nie przejdzie któregokolwiek z tych warunków, zostaje odrzucony, zanim otworzymy jakiekolwiek połączenie, i taka odmowa nigdy nie jest ponawiana, bo ponowienie niczego by nie naprawiło.

  1. Schemat musi być dokładnie https. Nie http, nie ftp, nie file. Po zwykłym HTTP podpisany artykuł szedłby otwartym tekstem, więc nie robimy tego nawet na prośbę.
  2. W adresie nie może być nazwy użytkownika ani hasła, czyli formy https://user:pass@host/.... Uwierzytelnieniem jest podpis, nie adres.
  3. Nazwa hosta musi się rozwiązywać. Pobieramy wszystkie rekordy A i AAAA, a adres podany wprost liczbowo sprawdzamy tak samo.
  4. Każdy adres, na który nazwa się rozwiązuje, musi być publiczny. Sprawdzamy rozwiązane adresy, a nie tekst URL, więc wycelowanie publicznej domeny w adres wewnętrzny nic nie daje. Jeśli nazwa zwraca kilka adresów i choć jeden z nich jest prywatny, odrzucamy cały cel.

Odrzucane zakresy adresów

Pełne listy, tak jak stoją w kodzie. Adresy IPv4 zapisane w formie IPv6, na przykład ::ffff:127.0.0.1, są rozpakowywane i sprawdzane jako IPv4.

IPv4 IPv6
0.0.0.0/8 ::/128
10.0.0.0/8 ::1/128
100.64.0.0/10 fc00::/7
127.0.0.0/8 fe80::/10
169.254.0.0/16 ff00::/8
172.16.0.0/12 2001:db8::/32
192.0.0.0/24 64:ff9b::/96
192.0.2.0/24
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/4
240.0.0.0/4

Powód jest prosty i nie jest teoretyczny. Adres endpointu wpisuje człowiek, a my strzelamy pod niego uwierzytelnionym żądaniem z wnętrza naszej sieci. Bez tej kontroli wystarczyłoby wskazać adres metadanych chmury albo lokalną bazę, żeby zrobić z silnika narzędzie do zaglądania nam do środka.

Odrzucony adres jest zawsze błędem ostatecznym, nigdy powtarzalnym. To pomyłka w konfiguracji, a kolejka dobijająca się do adresu wewnętrznego jest dokładnie tym, czego ta kontrola ma nie dopuścić.

2. Żądanie, które wysyłamy

Zawsze jedno żądanie POST pod adres, który zapisałeś, z treścią w JSON i czterema nagłówkami podpisu. Nigdy nie używamy innej metody, nie wysyłamy formularza ani danych wieloczęściowych i nigdy nie robimy więcej niż jedno żądanie na dostawę.

Kształt żądania

POST /articles HTTP/1.1
Host:                hooks.example.com
Content-Type:        application/json
Accept:              application/json
X-Signature-Version: v1
X-Timestamp:         1755859200
X-Nonce:             9f1c3b7e2a4d5061c8f30b9e7a1d2c4b
X-Signature:         2b7d4f9a1c60e8352da4bf07c19e6d8b5a03f2ce74198bd6e0a5c3f28b7d61a4

Nagłówki

Nagłówek Znaczenie
Content-Type: application/json Treść jest zawsze pojedynczym obiektem JSON zakodowanym w UTF-8. Nie uciekamy znaków spoza ASCII i nie uciekamy ukośników, więc polskie znaki i adresy zobaczysz w treści dosłownie.
Accept: application/json Prosimy o odpowiedź w JSON. Nie jest to warunek powodzenia: odpowiedź w innym formacie przyjmiemy, tylko nie znajdziemy w niej identyfikatora.
X-Signature-Version Wersja schematu podpisu. Dzisiaj zawsze v1. Inny schemat dostanie inną wartość, więc odrzucaj to, czego nie znasz.
X-Timestamp Czas uniksowy w sekundach z chwili, w której policzyliśmy podpis.
X-Nonce Trzydzieści dwa znaki szesnastkowe, losowane osobno dla każdego żądania. Nigdy nie powtarzamy tej wartości.
X-Signature HMAC-SHA256 zapisany jako 64 małe znaki szesnastkowe. Sposób liczenia opisuje sekcja o podpisie.

Na odpowiedź czekamy 15 sekund. Brak odpowiedzi w tym czasie traktujemy jak awarię przejściową i ponawiamy. Jeśli Twój odbiornik ma coś wolnego do zrobienia, przyjmij artykuł, odpowiedz od razu i zrób resztę w kolejce.

Podaj adres docelowy, a nie przekierowanie. Podpisujemy ścieżkę tego adresu, który zapisałeś, więc odbiornik stojący pod innym adresem po przekierowaniu policzy podpis z innej ścieżki i go nie potwierdzi.

Poza adresem i sekretem to połączenie nie ma żadnych ustawień. Limit czasu, liczba prób i odstępy między nimi są stałe po naszej stronie i nie da się ich zmienić dla pojedynczej strony.

3. Treść żądania

Treść żądania to jeden obiekt JSON. Te nazwy kluczy są kontraktem publicznym, a nie szczegółem naszej implementacji. W wersji 1 klucze mogą przybywać, ale żaden nie zostanie przemianowany ani usunięty; gdyby kiedyś trzeba było to złamać, zmieni się envelope_version. Napisz odbiornik tak, żeby nieznane klucze po prostu ignorował.

Przykładowe żądanie publikacji

{
  "envelope_version": "1",
  "event": "article.publish",
  "id": "0f6f9c22-6f1f-4e2a-9c33-0b1e6d8a9f4e",
  "occurred_at": "2026-08-22T09:15:04+00:00",
  "site": {
    "id": "412",
    "domain": "sklep.example"
  },
  "remote_id": null,
  "article": {
    "id": "918273",
    "title": "Jak wybrać dobry ekspres do kawy",
    "slug": "jak-wybrac-dobry-ekspres-do-kawy",
    "html": "<h2>Na co patrzeć</h2><p>Ciśnienie ...</p>",
    "excerpt": "Krótkie streszczenie, nadaje się na opis meta.",
    "featured_image_url": "https://images.example/cover.jpg",
    "categories": ["porady"],
    "tags": ["ekspres", "kawa", "sprzęt"],
    "canonical_url": "https://sklep.example/jak-wybrac-dobry-ekspres-do-kawy/",
    "published_at": "2026-08-22T09:15:04+00:00",
    "status": "publish"
  }
}

Pola koperty

Pole Typ Uwagi
envelope_version tekst Dzisiaj zawsze "1".
event tekst Jedno z trzech zdarzeń opisanych niżej.
id tekst, UUID Klucz idempotencji tej konkretnej dostawy. Ponowiona próba powtarza tę samą wartość, więc odbiornik, który ją zapamięta, rozpozna duplikat i może go pominąć.
occurred_at tekst, ISO 8601 Moment, w którym zbudowaliśmy wiadomość.
site.id tekst Nasz identyfikator Twojej strony. Stały.
site.domain tekst Sama nazwa hosta, bez schematu, bez ukośnika na końcu i bez www., zapisana małymi literami.
remote_id tekst albo null Przy publikacji zawsze null. Przy korekcie jest to identyfikator, który wcześniej od Ciebie dostaliśmy.
article obiekt albo null Przy teście połączenia null, w pozostałych przypadkach obiekt opisany w następnej tabeli.

Pola artykułu

Pole Typ Uwagi
article.id tekst Nasz identyfikator artykułu, stały przez całe jego życie. To jest ta jedna wartość, którą warto zapisać przy swoim wpisie.
article.title tekst Zwykły tekst, bez znaczników.
article.slug tekst Człon adresu, małe litery i myślniki. Nie gwarantujemy, że jest niepowtarzalny po Twojej stronie, więc jeśli tego potrzebujesz, rozstrzygnij konflikt sam.
article.html tekst Treść artykułu jako HTML. Nagłówki są na poziomie h2 i h3, a tytuł nie jest w środku powtórzony. Przefiltruj to przed wyrenderowaniem, tak jak każdy HTML z zewnątrz.
article.excerpt tekst Jedno albo dwa zdania zwykłego tekstu. Nadaje się na opis meta.
article.featured_image_url tekst albo null Pełny adres https do obrazka wiodącego, albo null, jeśli artykuł go nie ma.
article.categories lista tekstów Nazwy kategorii, nie identyfikatory. Zwykle jedna pozycja, czasem lista pusta.
article.tags lista tekstów Słowa kluczowe, bez powtórzeń, po obcięciu białych znaków. Może być pusta.
article.canonical_url tekst albo null Adres, pod którym artykuł ma mieszkać w Twojej domenie. Użyj go jako rel="canonical", jeśli renderujesz wpis także gdzie indziej.
article.published_at tekst, ISO 8601 Zamierzony moment publikacji.
article.status tekst Zawsze "publish". To połączenie nigdy nie wysyła szkicu, bo nie umiemy sprawdzić, czy Twój odbiornik faktycznie by go ukrył.

Trzy zdarzenia

Wartość pola event mówi, co masz zrobić. To jedyne trzy wartości, jakie wysyłamy.

Zdarzenie Pole article Pole remote_id Co masz zrobić
article.publish obiekt null Utwórz wpis.
article.update obiekt Twój identyfikator Zaktualizuj wpis o tym identyfikatorze. Jeśli już go nie ma, utwórz go na nowo.
connection.test null null Nie twórz niczego. Sprawdź podpis i odpowiedz kodem 2xx.

Zdarzenie connection.test wysyła przycisk testu połączenia w naszym panelu. Odbiornik, który utworzy z niego wpis, będzie produkował pusty artykuł za każdym razem, gdy ktoś sprawdzi swoje ustawienia. To najczęstszy błąd przy pisaniu odbiornika, więc obsłuż to zdarzenie zanim zajmiesz się resztą.

4. Jak sprawdzić podpis

Podpisujemy metodę, ścieżkę i skrót treści, a nie sam czas. Dzięki temu przechwycone żądanie nie da się odtworzyć z podmienionym artykułem. Podpis, którego nie da się sprawdzić, jest podpisem, który się ignoruje, więc poniżej jest wszystko, czego potrzebujesz, żeby policzyć go u siebie.

Ciąg kanoniczny

Sześć linii połączonych pojedynczym znakiem nowej linii. Bez znaku nowej linii na końcu.

v1
1755859200
9f1c3b7e2a4d5061c8f30b9e7a1d2c4b
POST
/articles
c1ba0d4b0e8f3a72d95e64b810cf27a3e0d5946bf1c83ea27d604b9f5138ace6

Skąd bierze się każda linia

  • Linia pierwsza to dosłowna wartość nagłówka X-Signature-Version, dzisiaj zawsze v1.
  • Linia druga to dosłowna wartość nagłówka X-Timestamp.
  • Linia trzecia to dosłowna wartość nagłówka X-Nonce.
  • Linia czwarta to metoda wielkimi literami, czyli zawsze POST.
  • Linia piąta to sama ścieżka Twojego adresu: bez schematu, bez hosta i bez zapytania, z ukośnikiem na początku. Dla https://hooks.przyklad.pl/artykuly jest to /artykuly. Jeśli adres nie ma ścieżki w ogóle, jest to /.
  • Zapytanie po znaku zapytania nie wchodzi do podpisu. Jeśli w adresie masz parametry, wyślemy je normalnie, ale nie są objęte podpisem, więc nie opieraj na nich niczego, co ma znaczenie dla bezpieczeństwa.
  • Linia szósta to SHA-256 surowych bajtów treści, zapisany szesnastkowo małymi literami. Nie parsuj JSON i nie koduj go ponownie przed policzeniem skrótu: kolejność kluczy i sposób uciekania znaków by się zmieniły i skrót przestałby się zgadzać.

Wzór

canonical = "v1" + "\n"
          + X-Timestamp + "\n"
          + X-Nonce + "\n"
          + "POST" + "\n"
          + path + "\n"
          + sha256_hex(raw_body)

X-Signature = hmac_sha256_hex(secret, canonical)

Co Twój odbiornik musi sprawdzić

Pięć kontroli. Cztery pierwsze są tanie, piąta jest tą, którą najczęściej robi się źle.

  1. Wszystkie cztery nagłówki podpisu są obecne. Brak któregokolwiek to odmowa.
  2. X-Signature-Version ma wartość v1. Nieznaną wersję odrzucaj, zamiast zgadywać.
  3. Różnica między X-Timestamp a Twoim czasem jest nie większa niż 300 sekund w którąkolwiek stronę.
  4. X-Nonce nie był jeszcze widziany. Trzymaj zużyte wartości przez 600 sekund; powtórzenie w tym oknie to próba odtworzenia żądania.
  5. Policzony podpis porównaj z otrzymanym w stałym czasie, funkcją typu hash_equals, crypto.timingSafeEqual albo hmac.compare_digest. Nigdy zwykłym porównaniem.

Kiedy którakolwiek kontrola nie przejdzie, odpowiedz kodem 401 i nie tłumacz, która to była. My pokażemy wtedy klientowi zdanie o niezgodnym sekrecie i nie ponowimy dostawy.

Weryfikacja w PHP

$raw = file_get_contents('php://input');

$canonical = implode("\n", [
    $request->header('X-Signature-Version'),
    $request->header('X-Timestamp'),
    $request->header('X-Nonce'),
    'POST',
    '/articles',
    hash('sha256', $raw),
]);

$expected = hash_hmac('sha256', $canonical, $secret);

abort_unless(
    hash_equals($expected, (string) $request->header('X-Signature')),
    401
);

Weryfikacja w Node

const crypto = require('crypto');

const canonical = [
  req.get('X-Signature-Version'),
  req.get('X-Timestamp'),
  req.get('X-Nonce'),
  'POST',
  '/articles',
  crypto.createHash('sha256').update(req.rawBody).digest('hex'),
].join('\n');

const expected = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
const given = Buffer.from(req.get('X-Signature') || '', 'hex');

if (given.length !== 32 ||
    !crypto.timingSafeEqual(Buffer.from(expected, 'hex'), given)) {
  return res.status(401).end();
}

Zmienna z treścią musi zawierać surowe, nieprzetworzone bajty. W Express uzyskasz je przez express.json({ verify: (req, res, buf) => { req.rawBody = buf } }).

Weryfikacja w Pythonie

import hashlib
import hmac

canonical = "\n".join([
    headers["X-Signature-Version"],
    headers["X-Timestamp"],
    headers["X-Nonce"],
    "POST",
    "/articles",
    hashlib.sha256(raw_body).hexdigest(),
])

expected = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

if not hmac.compare_digest(expected, headers.get("X-Signature", "")):
    return 401

Make, Zapier i n8n

Te narzędzia niechętnie oddają surową treść żądania, a ich własne kroki liczące HMAC są niewygodne. Masz dwie sensowne drogi.

  • Lepsza. Postaw przed scenariuszem małą funkcję, choćby na Cloudflare Workers albo jako funkcję bezserwerową, która sprawdzi podpis i dopiero potem przekaże dalej już zaufany JSON.
  • Do przyjęcia. Wskaż połączenie na adres webhooka na tyle długi i losowy, żeby nie dało się go zgadnąć, i potraktuj go jako jedyne zabezpieczenie. Tracisz wtedy ochronę przed odtworzeniem żądania i pewność, że treść nie została podmieniona. Powiedz sobie to na głos, zanim tak zrobisz.

5. Odpowiedź, której oczekujemy

Odpowiedź czytamy w dwóch krokach: najpierw rozstrzygamy, czy dostawa się udała, potem szukamy w niej identyfikatora wpisu.

Za powodzenie uznajemy każdy kod z zakresu 2xx i nic poza tym. Treść odpowiedzi nie wpływa na to rozstrzygnięcie: pusta odpowiedź z kodem 200 jest pełnym sukcesem.

Jak oddać nam identyfikator wpisu

Przy publikacji odpowiedz obiektem JSON z własnym identyfikatorem utworzonego wpisu. To ten identyfikator wróci do Ciebie w polu remote_id, kiedy artykuł będzie trzeba poprawić.

HTTP/1.1 200 OK
Content-Type: application/json

{ "id": "8821" }

Szukamy po kolei kluczy id, remote_id, post_id, data.id i data.remote_id, i bierzemy pierwszą wartość, która jest niepustym tekstem albo liczbą całkowitą.

Jeśli nie znajdziemy żadnego z nich, zapamiętujemy jako identyfikator zdalny nasze własne article.id, czyli tę samą wartość, którą już dostałeś w treści żądania. Jest to celowe, żeby goły kod 200 z narzędzia bez kodu też działał.

Ma to jedną konsekwencję i warto ją zrozumieć od razu. Jeśli utworzysz wpis pod własnym identyfikatorem i nam go nie oddasz, korekty przyjdą opisane naszym identyfikatorem artykułu, a nie Twoim. Albo więc oddawaj swój identyfikator przy każdej publikacji, albo zapisuj u siebie nasz.

Kształt, który się nie psuje

Najbezpieczniejszy odbiornik jest idempotentny: szuka wpisu po kluczu, aktualizuje go, gdy istnieje, i tworzy, gdy go nie ma. Przy takim kształcie ponowiona dostawa nie robi duplikatu, a korekta nie gubi się dlatego, że ktoś skasował wpis ręcznie.

Zalecany przebieg

key = remote_id or article.id

find post where external_id = key
    found     -> update it
    not found -> create it, external_id = key

6. Co robimy przy każdym błędzie

Twoja odpowiedź decyduje, czy artykuł wróci do kolejki, czy zostanie oznaczony jako niedostarczony. Rozróżnienie jest tu naprawdę istotne i nie warto go zgadywać.

Odpowiedź Co robimy
2xx Sukces. Zapisujemy identyfikator wpisu i kończymy.
401, 403 Błąd ostateczny. Nie ponawiamy. Klient widzi zdanie o odrzuconym podpisie i prośbę o sprawdzenie, czy obie strony mają ten sam sekret.
404 Błąd ostateczny. Nie ponawiamy. Klient widzi prośbę o sprawdzenie adresu.
429 Błąd ostateczny. Nie ponawiamy, mimo że kod sugeruje inaczej. Ta dostawa się nie powiedzie. Jeśli musisz ograniczać nasze tempo, lepiej przyjmij żądanie kodem 2xx i wyhamuj u siebie w kolejce.
400, 402, 405 ... 499 Błąd ostateczny. Nie ponawiamy, bo powtórzenie tego samego żądania da ten sam wynik.
500 ... 599 Awaria przejściowa. Ponawiamy z narastającym odstępem.
timeout, reset Awaria przejściowa. Ponawiamy tak samo jak przy błędzie serwera.
url refused Błąd ostateczny, i to bez wysłania żądania. Adres nie przeszedł kontroli opisanej w sekcji pierwszej.

Harmonogram ponowień

Awarię przejściową próbujemy dostarczyć najwyżej pięć razy. Po pierwszej nieudanej próbie odstępy wynoszą kolejno:

1 minuta, 5 minut, 15 minut, 30 minut

Ostatnia próba wypada więc mniej więcej pięćdziesiąt minut po pierwszej. Jeśli i ona się nie powiedzie, artykuł zostaje oznaczony jako niedostarczony i czeka na człowieka.

Błąd ostateczny nie dostaje żadnej dodatkowej próby. Zatrzymuje artykuł natychmiast, bo ponawianie odrzuconego podpisu albo błędnego adresu tylko zużyłoby próby i niczego nie naprawiło.

Kiedy odrzucisz dostawę, zapisujemy u siebie kod odpowiedzi i pierwsze 500 znaków jej treści. Jeśli chcesz, żeby wsparcie mogło Ci szybko pomóc, zmieść w tym miejscu krótki, konkretny powód odmowy. Nie potrzebujemy śladu stosu z Twojego frameworka.

Co robimy ze zdjęciami

Nie wysyłamy żadnych plików. Cała dostawa to jedno żądanie JSON, nigdy nie ma w niej danych wieloczęściowych ani obrazka zakodowanego w treści, i nigdy nie wgrywamy niczego do Twojej biblioteki mediów.

Obrazek wiodący dostajesz jako featured_image_url, czyli pełny adres https, albo jako null, jeśli artykuł go nie ma.

Obrazki wewnątrz treści zostają tam, gdzie były: jako zwykłe znaczniki w polu html, z adresami wskazującymi poza Twoją stronę.

Zalecamy przy odbiorze skopiować obrazki do własnego magazynu i podmienić adresy na swoje. Nie obiecujemy, że te adresy będą działać wiecznie, a artykuł z pustymi prostokątami zamiast zdjęć wygląda gorzej niż artykuł bez zdjęć.

Co robimy, a czego nie zrobimy nigdy

Co robimy

  • Wysyłamy jedno żądanie POST pod adres, który zapisałeś, i nic poza tym.
  • Podpisujemy każde żądanie, łącznie z testowym, tym samym schematem i tym samym sekretem.
  • Sprawdzamy adres przed każdym żądaniem i odmawiamy wysyłki pod adres wewnętrzny.
  • Zapisujemy identyfikator wpisu, który nam oddasz, i odsyłamy go przy każdej korekcie.
  • Ponawiamy dostawę, kiedy Twój serwer chwilowo nie odpowiada.
  • Wysyłamy podpisany ping, który niczego nie tworzy, kiedy klient sprawdza połączenie.

Czego nie zrobimy nigdy

  • Nie czytamy katalogu produktów. Za webhookiem nie ma katalogu, więc to połączenie tylko publikuje.
  • Nie wysyłamy szkiców. Pole statusu zawsze mówi publish, bo nie umiemy zagwarantować, że Twój odbiornik ukryłby szkic.
  • Nie wgrywamy plików i nie wysyłamy danych wieloczęściowych.
  • Nie łączymy się po zwykłym HTTP, nawet na wyraźną prośbę.
  • Nie wysyłamy niczego pod adres w sieci wewnętrznej ani pod domenę, która na taki adres wskazuje.
  • Nie kasujemy niczego u Ciebie. Wysyłamy wyłącznie publikacje, korekty i test.
  • Nie wysyłamy sekretu w treści ani w nagłówku i nie zapisujemy go w logach.

Kiedy coś nie działa

Zdania z lewej kolumny widzi klient w panelu. Z prawej jest to, co naprawdę się stało, i co z tym zrobić po stronie odbiornika.

Objaw Przyczyna i co zrobić
To połączenie nie ma adresu docelowego. Pole adresu jest puste, więc nie ma dokąd wysłać żądania i nie wysyłamy żadnego. Uzupełnij adres endpointu i zapisz połączenie ponownie.
To połączenie nie ma sekretu do podpisu. Bez sekretu nie policzymy podpisu, a Twój odbiornik nie miałby czym nas zweryfikować, więc nie wysyłamy nic. Wygeneruj sekret i zapisz tę samą wartość po obu stronach.
Adres musi być adresem https. Podany adres nie zaczyna się od https://. Po zwykłym HTTP podpisany artykuł szedłby otwartym tekstem. Nie jest to ograniczenie, które zdejmujemy na prośbę.
Usuń nazwę użytkownika i hasło z adresu. W adresie jest forma user:pass@host. Uwierzytelnieniem jest podpis, a nie adres. Jeśli Twój odbiornik stoi za uwierzytelnianiem podstawowym, zdejmij je z tej ścieżki i oprzyj się na podpisie.
Ta nazwa hosta się nie rozwiązuje. DNS nie zwrócił dla niej żadnego adresu. Sprawdź, czy nazwa nie ma literówki i czy strefa jest opublikowana. Świeżo dodana nazwa potrafi jeszcze nie być widoczna wszędzie.
Ten adres prowadzi do sieci wewnętrznej. Nazwa rozwiązała się na adres z odrzucanego zakresu. Zdarza się to także wtedy, gdy publiczna domena ma jeden rekord wskazujący do środka: wystarczy jeden taki adres wśród kilku, żeby odrzucić cały cel. Wystaw odbiornik pod adresem osiągalnym publicznie.
Twój endpoint odrzucił nasz podpis. Odpowiedziałeś kodem 401 albo 403. Najczęściej powodem jest inny sekret po jednej ze stron, policzenie skrótu z treści już sparsowanej i ponownie zakodowanej, albo użycie w podpisie pełnego adresu zamiast samej ścieżki. Sprawdź to w tej kolejności.
Twój endpoint odpowiedział kodem 404. Pod tą ścieżką nic nie nasłuchuje. Sprawdź adres, a zwłaszcza to, czy trasa przyjmuje metodę POST, bo trasa zdefiniowana tylko dla GET daje w wielu frameworkach dokładnie ten kod.
Twój endpoint poprosił nas o zwolnienie. Odpowiedziałeś kodem 429. Ta dostawa jest stracona, bo tego kodu nie ponawiamy. Jeśli musisz nas ograniczać, przyjmij żądanie kodem 2xx i wyhamuj we własnej kolejce.
Twój endpoint odpowiedział błędem serwera. Kod z zakresu 5xx. Uznajemy to za awarię przejściową i ponawiamy z narastającym odstępem, więc jednorazowa czkawka niczego nie kosztuje. Jeśli powtarza się przy każdej próbie, zajrzyj do własnego logu.
Nie udało się dotrzeć do tego adresu w czasie. Nie odpowiedziałeś w ciągu 15 sekund albo połączenie zerwało się w trakcie. Ponawiamy. Jeśli zdarza się stale, znaczy że odbiornik robi za dużo w samym żądaniu: przyjmij artykuł, odpowiedz od razu, resztę przenieś do kolejki.
Twój endpoint odrzucił artykuł innym kodem. Każdy inny kod spoza 2xx traktujemy jako odmowę ostateczną. Podajemy numer w komunikacie, a w naszym logu mamy pierwsze 500 znaków Twojej odpowiedzi, więc krótki powód odmowy w treści bardzo przyspiesza rozmowę ze wsparciem.
Po każdym teście połączenia pojawia się pusty wpis. Odbiornik traktuje zdarzenie connection.test jak publikację. W tym zdarzeniu pole article jest puste. Sprawdź podpis, nie twórz niczego i odpowiedz kodem 2xx.
Ten sam artykuł pojawia się u mnie kilka razy. Odbiornik tworzy nowy wpis przy każdej dostawie. Zapisuj klucz id z koperty i pomijaj powtórzenia, a wpisy wiąż z article.id albo oddawaj nam swój identyfikator przy każdej publikacji.
Korekty przychodzą z identyfikatorem, którego nie znam. Przy publikacji nie oddałeś swojego identyfikatora, więc zapamiętaliśmy nasz i to jego odsyłamy w polu remote_id. Albo zacznij oddawać swój identyfikator, albo zapisuj nasz przy wpisie.
Artykuł pojawił się bez zdjęcia. Nie wgrywamy plików. Obrazek wiodący jest adresem w polu featured_image_url, a zdjęcia w treści to zwykłe znaczniki w polu html. To odbiornik decyduje, czy je pobrać i zapisać u siebie.
Nie ma synchronizacji produktów. To nie jest usterka. Za webhookiem nie ma katalogu do odczytania, więc to połączenie wyłącznie publikuje i nie oferujemy przy nim synchronizacji produktów.

Lista kontrolna odbiornika

Przejdź to przed pierwszą publikacją. Każdy punkt odpowiada jednej rzeczy, która potrafi się zepsuć cicho.

  • Czytasz surową treść żądania przed jakimkolwiek parsowaniem i liczysz skrót z dokładnie tych bajtów.
  • Odrzucasz brakującą albo nieznaną wartość X-Signature-Version.
  • Odrzucasz znacznik czasu odległy o więcej niż 300 sekund.
  • Trzymasz zużyte wartości X-Nonce przez 600 sekund i odrzucasz powtórzenie.
  • Porównujesz podpisy funkcją działającą w stałym czasie.
  • Na zdarzenie connection.test odpowiadasz kodem 2xx i nie tworzysz niczego.
  • Wiążesz wpisy z article.id albo oddajesz nam swój identyfikator przy każdej publikacji.
  • Pomijasz dostawę, której klucz id już przetworzyłeś.
  • Ignorujesz nieznane klucze, żeby przyszłe dodanie pola niczego nie zepsuło.
  • Filtrujesz pole html przed wyrenderowaniem.
  • Odpowiadasz w czasie krótszym niż 15 sekund, a wolną pracę przenosisz do kolejki.
  • Twój certyfikat jest ważny i łańcuch jest kompletny, bo łączymy się wyłącznie po https.