Zmiany w API

Changelogi webhooków: zmiana łamiąca bez prośby

5 min czytania

Changelog API REST istnieje, bo wywołujący może odrzucić odpowiedź, której nie rozumie, albo przynajmniej zalogować błąd na tyle głośno, żeby ktoś to zauważył. Odbiorca webhooka rzadko robi jedno albo drugie. Dostaje POST, czyta oczekiwane pola, a jeśli pole się przesunęło, zmieniło typ albo zniknęło, endpoint albo pada po cichu wewnątrz zadania w tle, którego nikt nie obserwuje, albo, gorzej, działa dalej z błędną wartością, której nigdy nie zwalidował. Czym jest zmiana łamiąca opisuje ogólną definicję; payload webhooka potrzebuje własnej odpowiedzi, bo tryb awarii jest inny niż w przypadku endpointu, który ktoś wywołuje celowo.

Dlaczego zmiana payloadu webhooka psuje się inaczej niż zmiana odpowiedzi API?

Bo kierunek żądania jest odwrócony. Wywołujący REST inicjuje wywołanie i może dodać nagłówek wersji, ponowić przy 4xx, albo przeczytać ostrzeżenie o wycofaniu w odpowiedzi. Odbiorca webhooka nie zainicjował niczego z tego: to wasz serwer zdecydował o wysłaniu, zdecydował kiedy i zdecydował, jaki kształt będzie miało body. Jedyną dźwignią odbiorcy jest walidacja, którą napisał podczas budowania integracji, a większość integracji buduje się raz, działają, i nikt do nich nie wraca, dopóki się nie zepsują. Ta asymetria to cały powód, dla którego zmiana payloadu webhooka zasługuje na więcej ostrożności niż taka sama zmiana w body odpowiedzi, o którą wywołujący aktywnie prosił.

Co naprawdę liczy się jako zmiana łamiąca w payloadzie webhooka?

ZmianaŁamiąca dla większości odbiorców
Dodanie nowego polaNie, jeśli odbiorcy ignorują nieznane pola (zweryfikujcie to założenie, nie zakładajcie)
Usunięcie polaTak, jeśli cokolwiek je czyta
Zmiana nazwy polaTak, funkcjonalnie identyczne z usunięciem starego
Zmiana typu pola (string na obiekt)Tak, prawie zawsze
Zmiana kolejności pól w body JSONNie, dla każdego odbiorcy parsującego po kluczu, a wszyscy powinni
Zmiana nazwy lub typu zdarzeniaTak, jeśli odbiorcy filtrują lub routują na tej podstawie

Wiersz “dodanie pola jest bezpieczne” to ten, na którym zespoły polegają najbardziej i ten, który najbardziej opłaca się zweryfikować, a nie założyć. Permisywny parser JSON domyślnie ignoruje nieznane pola, ale odbiorca deserializujący do ścisłego schematu, kilka języków typowanych robi to bez dodatkowej konfiguracji, może odrzucić cały payload, gdy tylko pojawi się nieoczekiwane pole. Dodanie pola jest bezpieczne dla waszego webhooka tylko wtedy, gdy wiecie, jak odbiorcy parsują, a nie dlatego, że sam JSON jest permisywny.

Jak nadać wersję payloadowi webhooka?

Podobnie jak przy odpowiedzi API, z jedną różnicą: odbiorca nigdy nie wysyła żądania, więc nie może poprosić o wersję, i to nadawca musi ją podać. Może trafić do body albo do nagłówka żądania samej dostawy; dostawy GitHuba niosą X-GitHub-Event i X-GitHub-Hook-ID, a specyfikacja Standard Webhooks umieszcza swoje metadane w nagłówkach webhook-*. Pole wersji w payloadzie ("payload_version": 2) to najtańsza opcja i działa, gdy odbiorcy są gotowi na jej podstawie się rozgałęziać. Wersjonowany typ zdarzenia (invoice.updated staje się invoice.updated.v2 jako odrębne zdarzenie, na które odbiorca dobrowolnie się zapisuje) wymaga więcej pracy przy budowie, ale oznacza, że stary kształt nadal płynie do tych, którzy nigdy nie zmigrowali, co liczy się tu bardziej niż w endpoincie REST, bo nie możecie zadzwonić do każdego odbiorcy z prośbą o aktualizację. Ustawienie na subskrypcję, wybrane przy rejestracji endpointu webhooka, wysuwa decyzję do przodu zamiast rozgałęziać przy każdej dostawie, i jest właściwym wyborem, gdy macie już rekord subskrypcji, do którego można ją dołączyć.

POST /endpoint-odbiorcy
{
  "event": "invoice.updated",
  "payload_version": 2,
  "data": { "invoice_id": "inv_123", "status": "paid" }
}

Skąd w ogóle wiecie, kto słucha?

Gorzej niż odpowiednik tego problemu w changelogu API, bo webhook nie ma po waszej stronie logu przychodzących żądań, który nazwałby wywołującego; macie tylko własny log wychodzących dostaw, który mówi, że endpoint dostał 200, nie co zrobił z body. Śledźcie co najmniej dwie rzeczy: każdy zarejestrowany endpoint z właścicielem, tę samą dyscyplinę, którą changelogi API wewnętrznego zalecają dla konsumentów wewnętrznych, i wasz wskaźnik nieudanych dostaw na endpoint po zmianie payloadu. Skok odpowiedzi 4xx lub 5xx z endpointu zaraz po zmianie to najbliższe temu, co dostaniecie zamiast stack trace, i często jedyny sygnał, że odbiorca się zepsuł, bo zespół, który go obsługuje, może tego nie zauważyć przez wiele dni.

Czy changelog webhooków powinien być osobny od changelogu API?

Osobna sekcja na tej samej stronie, nie osobna publikacja. Changelog API już ustala, kto go czyta i jak się subskrybuje; zmiana payloadu webhooka należy do tego samego strumienia, oznaczona wystarczająco wyraźnie, żeby developerka po stronie odbiorcy skanująca “czy to wpływa na moją integrację” mogła to przefiltrować, bo konsumentka webhooka często nie ma innego powodu, żeby sprawdzać ogólny changelog API, i znajdzie go tylko wtedy, gdy ktoś ją tam bezpośrednio skieruje.

Jak powinno wyglądać rozsądne okno wycofania dla payloadu webhooka?

Dłuższe niż odpowiednie wycofanie REST, bo migracja po stronie odbiorcy zwykle oznacza, że drugi zespół, z którym możecie nie mieć bezpośredniego kontaktu, musi to zauważyć, zaplanować i wydać bez własnej pilności. Miesiąc to rozsądne minimum dla pola, które odbiorca prawdopodobnie nadal parsuje permisywną biblioteką; trzy miesiące lub więcej są bezpieczniejsze przy usunięciu pola, które ścisły schemat całkowicie by odrzucił. Wysyłajcie stary i nowy kształt razem podczas okna, gdy to wykonalne (stare pole status i jego zamiennik z wersji 2 w tym samym payloadzie), bo odbiorca czytający stare pole działa dalej bez dotykania kodu, a ten, który już zmigrował, po prostu ignoruje pole, którego już nie potrzebuje.

FAQ

Czy konsumenci webhooków muszą potwierdzić zmianę payloadu przed jej wdrożeniem? Domyślnie nie istnieje mechanizm potwierdzenia, i właśnie dlatego okno wycofania liczy się tu bardziej niż w API REST: nikt nie potwierdza gotowości, więc okno musi być wystarczająco długie, by większość odbiorców zmigrowała we własnym tempie, zanim stary kształt zniknie.

Czy kiedykolwiek bezpiecznie jest dodać nieznane pola bez uprzedzenia? Tylko po zweryfikowaniu, nie założeniu, że wasi odbiorcy parsują permisywnie. Wpis w changelogu kosztuje niewiele i usuwa niepewność; ciche dodawanie pól przy założeniu, że “parsery JSON ignorują dodatki”, psuje każdego odbiorcę ze ścisłą deserializacją.

Jaki jest najszybszy sposób na wykrycie zepsutego odbiorcy webhooka po zmianie payloadu? Wskaźnik nieudanych dostaw na endpoint, obserwowany w godzinach zaraz po zmianie. Nie powie wam, co się zepsuło, tylko że coś się zepsuło, ale to najwcześniejszy i często jedyny sygnał, jaki dostaniecie.

Czy logika ponawiania pomaga odbiorcom przetrwać zmianę payloadu? Nie. Ponowienie wysyła ten sam nowy payload jeszcze raz; nie wraca do kształtu, który odbiorca może sparsować. Zmiana payloadu psuje odbiorcę przy pierwszej dostawie i każdym kolejnym ponowieniu identycznie.


Twierdzenia techniczne w tym artykule nie zostały niezależnie zweryfikowane. Jeśli coś się nie zgadza, daj nam znać, a poprawimy to.

Powiązane w changeloop: Dokumentacja dla deweloperów, Porównanie narzędzi do changeloga

changeloop
Zespół, który tworzy changelog zamykający pętlę. Użytkownicy o coś proszą, Twój zespół to dostarcza, proszący się dowiaduje.