Webhook: co to jest i jak połączyć AI z formularzem, mailem i arkuszem
Czym jest webhook i jak go użyć z AI: działający serwer w Pythonie, test na trzech zgłoszeniach, zabezpieczenie sekretem i wersje w Make i Zapier.

👁 116 przeczytań
- Webhook to adres URL, pod który zewnętrzna aplikacja wysyła dane HTTP POST od razu po zdarzeniu, zamiast czekać na cykliczne odpytywanie co kilka minut.
- Opisany serwer oparty na FastAPI i modelu openai/gpt-5.6-luna przetwarza jedno zgłoszenie w 1,7-2,1 s przy koszcie około 0,00016 USD.
- W Zapierze webhooki są dostępne dopiero w planach płatnych od 19,99 USD miesięcznie (przy rozliczeniu rocznym), a plan darmowy pozwala na 100 zadań miesięcznie i tylko dwukrokowe Zapy.
Webhook to adres URL, pod który jedna aplikacja sama wysyła dane do drugiej w chwili, gdy coś się wydarzy: ktoś wypełnił formularz, przyszedł mail, ktoś zapłacił. Zamiast co minutę pytać „czy jest coś nowego?”, czekasz, aż ktoś zapuka. Poniżej pokazuję webhook w praktyce: lokalny serwer w Pythonie (FastAPI) odbiera zgłoszenie z formularza, model AI je klasyfikuje i pisze szkic odpowiedzi, a wynik trafia do arkusza. Potem to samo opisuję w Make i Zapier. Tekst jest częścią ścieżki Agenci AI od zera, etap automatyzacji.
Stan na 4 października 2026
- Test: Windows 11, Python 3.13, FastAPI 0.142.2, uvicorn 0.54.0, model
openai/gpt-5.6-lunaprzez OpenRouter. - Jedno zgłoszenie: 1,7-2,1 s od wysłania do odpowiedzi, koszt około 0,00016 USD.
- Make: plan darmowy to 1000 kredytów miesięcznie i 2 aktywne scenariusze. Zapier: 100 zadań miesięcznie, tylko Zapy dwukrokowe, a webhooki są w planach płatnych (od 19,99 USD miesięcznie przy płatności rocznej).
- Kod: paczka promptowy-agenci-starter, pliki
06_webhook.pyi06_test_webhook.py.
Webhook, prostymi słowami
Są dwa sposoby, żeby program dowiedział się o czymś nowym:
- pytanie co jakiś czas (polling): Twój program co 5 minut zagląda do skrzynki albo arkusza i sprawdza, czy coś przybyło. Proste, ale opóźnione i marnuje zapytania;
- webhook: dajesz innej aplikacji swój adres (np.
https://twojadomena.pl/webhook/formularz), a ona wysyła tam zapytanie HTTP POST z danymi w JSON od razu, gdy coś się stanie.
Webhook to zwykłe zapytanie do API, tylko w odwrotną stronę: to nie Ty wołasz serwis, to serwis woła Ciebie. Jeśli zapytania HTTP i JSON to dla Ciebie nowość, zacznij od przewodnika po API.
Typowe źródła webhooków:
- formularze (Tally, Typeform, Formularze Google przez skrypt, formularz na WordPressie);
- płatności (Stripe, PayU, Przelewy24 powiadamiają o zapłacie);
- sklepy (WooCommerce, Shopify: nowe zamówienie);
- narzędzia do pracy (GitHub, Slack, Trello, Calendly);
- automatyzacje (Make, Zapier i n8n mają gotowe moduły „odbierz webhook”).
Co zbudowałem
- Formularz kontaktowy wysyła JSON: imię, e-mail, wiadomość.
- Mój serwer sprawdza sekret w nagłówku (żeby obcy nie mogli wysyłać śmieci).
- Model AI dostaje wiadomość i zwraca JSON: kategoria, pilność od 1 do 3, streszczenie, szkic odpowiedzi.
- Serwer dopisuje wiersz do pliku CSV (lokalny odpowiednik arkusza Google) i odsyła wynik.
Kod serwera webhooka
Instalacja: pip install fastapi uvicorn openai python-dotenv. W pliku .env klucz OPENROUTER_API_KEY i WEBHOOK_SECRET (długi losowy ciąg).
"""06 - Webhook: formularz wysyła zgłoszenie -> serwer pyta model -> wynik trafia do arkusza (CSV) i wraca w odpowiedzi.
Start: uvicorn 06_webhook:app --port 8000 (albo: python 06_webhook.py)
Test: curl -X POST http://127.0.0.1:8000/webhook/formularz -H "X-Webhook-Secret: ..." -H "Content-Type: application/json"
-d "{\"imie\":\"Ola\",\"email\":\"[email protected]\",\"wiadomosc\":\"Faktura ma zły NIP\"}"
"""
import csv, datetime, json, os, pathlib
from dotenv import load_dotenv
from fastapi import FastAPI, Header, HTTPException
from openai import OpenAI
from pydantic import BaseModel
load_dotenv()
SEKRET = os.getenv("WEBHOOK_SECRET", "zmien-mnie") # ten sam ciąg wpisujesz w Make/Zapier/formularzu
MODEL = os.getenv("MODEL", "openai/gpt-5.6-luna")
ARKUSZ = pathlib.Path("zgloszenia.csv")
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])
app = FastAPI()
class Zgloszenie(BaseModel): # tak wygląda JSON, który wysyła formularz
imie: str
email: str
wiadomosc: str
@app.post("/webhook/formularz")
def odbierz(z: Zgloszenie, x_webhook_secret: str = Header(default="")):
if x_webhook_secret != SEKRET: # bez tego każdy, kto zna adres, może wysyłać Ci śmieci i palić tokeny
raise HTTPException(status_code=401, detail="zły sekret")
r = client.chat.completions.create(model=MODEL, response_format={"type": "json_object"}, messages=[
{"role": "system", "content": "Jesteś asystentem biura obsługi. Zwróć JSON z polami: kategoria (faktury|reklamacja|"
"oferta|inne), pilnosc (1-3, 3 = pilne), streszczenie (max 12 słów), szkic_odpowiedzi (2-3 zdania, po polsku, na Ty)."},
{"role": "user", "content": f"Od: {z.imie}\nWiadomość: {z.wiadomosc}"}])
wynik = json.loads(r.choices[0].message.content)
nowy = not ARKUSZ.exists()
with ARKUSZ.open("a", newline="", encoding="utf-8-sig") as f: # utf-8-sig: Excel poprawnie pokaże polskie znaki
w = csv.writer(f, delimiter=";")
if nowy: w.writerow(["czas", "imie", "email", "kategoria", "pilnosc", "streszczenie", "szkic_odpowiedzi"])
w.writerow([datetime.datetime.now().isoformat(timespec="seconds"), z.imie, z.email, wynik.get("kategoria"),
wynik.get("pilnosc"), wynik.get("streszczenie"), wynik.get("szkic_odpowiedzi")])
return {"ok": True, **wynik, "koszt_usd": getattr(r.usage, "cost", None)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)Kluczowe miejsca:
class Zgloszenie(BaseModel)opisuje, jak ma wyglądać JSON. FastAPI samo odrzuci zapytanie bez wymaganych pól (błąd 422), zanim zapłacisz za model;- sekret w nagłówku
X-Webhook-Secret. Zgadnięty adres bez sekretu dostaje 401 i nie kosztuje nic; response_format={"type": "json_object"}każe modelowi oddać czysty JSON, który od razu wpisuję w kolumny;utf-8-sigprzy zapisie CSV sprawia, że Excel pokazuje polskie znaki bez kombinowania.
Uruchomienie i test
W jednym oknie terminala startuję serwer: uvicorn 06_webhook:app --port 8000. W drugim uruchamiam skrypt, który udaje formularz i wysyła trzy zgłoszenia plus jedno z błędnym sekretem:
"""Wysyła 3 przykładowe zgłoszenia do lokalnego webhooka (tak samo zrobiłby to formularz albo Make/Zapier)."""
import os, time, requests
URL = "http://127.0.0.1:8000/webhook/formularz"
SEKRET = os.getenv("WEBHOOK_SECRET", "zmien-mnie")
print("zły sekret ->", requests.post(URL, json={"imie": "X", "email": "[email protected]", "wiadomosc": "test"},
headers={"X-Webhook-Secret": "zgaduje"}).status_code)
for imie, tresc in [("Ola", "Dzień dobry, na fakturze FV/2026/09/027 jest zły NIP mojej firmy. Potrzebuję korekty do piątku, bo zamykam miesiąc."),
("Marek", "Ile kosztuje wdrożenie chatbota na stronę sklepu z 300 produktami? Interesuje mnie termin na listopad."),
("Kasia", "Zamówiony kabel przyszedł uszkodzony, wtyczka jest pęknięta. Chcę zwrot pieniędzy.")]:
t = time.time()
r = requests.post(URL, json={"imie": imie, "email": f"{imie.lower()}@example.com", "wiadomosc": tresc},
headers={"X-Webhook-Secret": SEKRET}, timeout=60)
print(f"\n{imie}: {tresc}\n-> HTTP {r.status_code} w {time.time() - t:.1f} s: {r.json()}")Wynik z 4 października, 14:11:
> python 06_test_webhook.py
zły sekret -> 401
Ola: Dzień dobry, na fakturze FV/2026/09/027 jest zły NIP mojej firmy. Potrzebuję korekty do piątku, bo zamykam miesiąc.
-> HTTP 200 w 2.1 s: {'ok': True, 'kategoria': 'faktury', 'pilnosc': 3, 'streszczenie': 'Błędny NIP na fakturze, potrzebna korekta do piątku', 'szkic_odpowiedzi': 'Dzięki za wiadomość - sprawdzimy fakturę FV/2026/09/027 i przygotujemy korektę z prawidłowym NIP-em. Zajmiemy się tym pilnie, aby dokument był gotowy do piątku.', 'koszt_usd': 0.0001746}
Marek: Ile kosztuje wdrożenie chatbota na stronę sklepu z 300 produktami? Interesuje mnie termin na listopad.
-> HTTP 200 w 1.8 s: {'ok': True, 'kategoria': 'oferta', 'pilnosc': 2, 'streszczenie': 'Zapytanie o koszt i termin wdrożenia chatbota dla sklepu', 'szkic_odpowiedzi': 'Cześć Marku, przygotujemy wycenę wdrożenia chatbota dla sklepu z 300 produktami oraz sprawdzimy dostępność terminu w listopadzie. Podeślij proszę adres sklepu i napisz, jakie funkcje chatbot powinien obsługiwać.', 'koszt_usd': 0.000162}
Kasia: Zamówiony kabel przyszedł uszkodzony, wtyczka jest pęknięta. Chcę zwrot pieniędzy.
-> HTTP 200 w 1.7 s: {'ok': True, 'kategoria': 'reklamacja', 'pilnosc': 2, 'streszczenie': 'Uszkodzony kabel, pęknięta wtyczka; klientka chce zwrot pieniędzy.', 'szkic_odpowiedzi': 'Przykro nam, że kabel dotarł uszkodzony. Prześlij proszę zdjęcia uszkodzenia oraz numer zamówienia, a zajmiemy się zwrotem pieniędzy.', 'koszt_usd': 0.0001504}
> type zgloszenia.csv
czas;imie;email;kategoria;pilnosc;streszczenie;szkic_odpowiedzi
2026-10-04T14:11:16;Ola;[email protected];faktury;3;Błędny NIP na fakturze, potrzebna korekta do piątku;Dzięki za wiadomość - sprawdzimy fakturę FV/2026/09/027 i przygotujemy korektę z prawidłowym NIP-em. Zajmiemy się tym pilnie, aby dokument był gotowy do piątku.
2026-10-04T14:11:18;Marek;[email protected];oferta;2;Zapytanie o koszt i termin wdrożenia chatbota dla sklepu;Cześć Marku, przygotujemy wycenę wdrożenia chatbota dla sklepu z 300 produktami oraz sprawdzimy dostępność terminu w listopadzie. Podeślij proszę adres sklepu i napisz, jakie funkcje chatbot powinien obsługiwać.
2026-10-04T14:11:19;Kasia;[email protected];reklamacja;2;"Uszkodzony kabel, pęknięta wtyczka; klientka chce zwrot pieniędzy.";Przykro nam, że kabel dotarł uszkodzony. Prześlij proszę zdjęcia uszkodzenia oraz numer zamówienia, a zajmiemy się zwrotem pieniędzy.Co widać:
- zapytanie ze złym sekretem odbite kodem 401, bez wywołania modelu;
- kategorie trafione we wszystkich trzech przypadkach. Pilność 3 dostała tylko sprawa z terminem („do piątku”), reszta 2;
- szkice odpowiedzi są poprawne, a w reklamacji model sam poprosił o zdjęcia i numer zamówienia;
- model raz wstawił do szkicu długi myślnik zamiast zwykłego. Jeśli masz zasady stylu, dopisz je do polecenia systemowego.
Pułapka, na którą trafiłem: curl w Git Bash na Windows z polskimi znakami wpisanymi w -d '...' wysłał zepsute kodowanie i serwer odpowiadał „There was an error parsing the body” (400). Pomogło zapisanie JSON-a do pliku i wysłanie go przez --data-binary @plik.json albo skrypt w Pythonie jak wyżej.
Jak wystawić webhook do internetu
Mam to spisane w całości
Zebrałem te rzeczy w ebookach, które możesz pobrać za darmo - bez zapisu na listę, bez haczyka.
Serwer na 127.0.0.1 widzi tylko Twój komputer. Żeby formularz z internetu mógł do niego zapukać, potrzebujesz publicznego adresu:
- do testów: tunel typu ngrok albo Cloudflare Tunnel, który daje tymczasowy adres HTTPS kierujący na Twój port 8000;
- na stałe: mały serwer albo platforma do aplikacji, np. Railway, gdzie wrzucasz ten sam plik i dostajesz adres;
- bez serwera: odbierz webhook w Make, Zapier albo n8n (opis niżej) i z nich zawołaj model.
Zawsze używaj HTTPS i sekretu. Poważne serwisy (Stripe, GitHub) dodatkowo podpisują każde zapytanie kluczem HMAC. Wtedy zamiast porównywać sekret, liczysz podpis treści i porównujesz z nagłówkiem.
To samo w Make (bez kodu)
- Utwórz scenariusz i jako pierwszy moduł dodaj Webhooks, Custom webhook. Make wygeneruje adres URL.
- Wklej adres w ustawieniach formularza (Tally, Typeform, WordPress) i wyślij jedno testowe zgłoszenie. Make sam rozpozna strukturę JSON.
- Dodaj moduł AI: OpenAI (albo moduł HTTP do OpenRouter) z poleceniem jak w moim kodzie i odpowiedzią w JSON.
- Dodaj JSON, Parse JSON, żeby rozbić odpowiedź na pola.
- Dodaj Google Sheets, Add a row i przypisz pola do kolumn.
- Opcjonalnie: filtr „pilnosc = 3” i moduł wysyłający powiadomienie.
Każde zgłoszenie zużyje w Make kilka kredytów (po jednym na moduł). Plan darmowy daje 1000 kredytów miesięcznie i 2 aktywne scenariusze. Więcej o Make w tekście Make: jak działa i za co płacisz.
To samo w Zapier
- Trigger: Webhooks by Zapier, Catch Hook (adres do wklejenia w formularz). Alternatywnie gotowy trigger formularza, jeśli Zapier go obsługuje.
- Akcja: ChatGPT albo AI by Zapier z poleceniem klasyfikacji.
- Akcja: Google Sheets, Create Spreadsheet Row.
Webhooks by Zapier to aplikacja premium, więc potrzebujesz planu płatnego (Professional od 19,99 USD miesięcznie przy płatności rocznej, 29,99 USD przy miesięcznej), a cały Zap ma 3 kroki, czyli więcej niż pozwala plan darmowy. Porównanie Zapier, Make i n8n jest w tekście Zapier: co to jest i ile kosztuje.
Kod, Make czy Zapier: co wybrać
| Sposób | Koszt stały | Czego wymaga | Dla kogo |
|---|---|---|---|
| Własny serwer (FastAPI) | hosting, od 0 zł na własnym komputerze z tunelem | podstaw Pythona | chcesz rozumieć, co się dzieje, i nie płacić za kroki |
| n8n (własny serwer) | darmowa edycja Community, płacisz za serwer | postawienia n8n, np. na VPS | dużo automatyzacji, dane u siebie |
| Make | od 0 USD (1000 kredytów) | tylko przeglądarki | średnio złożone scenariusze, rozgałęzienia |
| Zapier | webhooki od planu Professional | tylko przeglądarki | najwięcej gotowych integracji, najprostszy start |
Najczęstsze pytania
Webhook co to jest w prostych słowach?
Adres internetowy, pod który inna aplikacja automatycznie wysyła informację, że coś się wydarzyło, razem z danymi tego zdarzenia.
Czym się różni webhook od API?
API to drzwi, do których Ty pukasz, kiedy czegoś potrzebujesz. Webhook to dzwonek, który dzwoni u Ciebie, kiedy coś się wydarzy po drugiej stronie. Technicznie webhook jest zapytaniem do Twojego API.
Czy webhook jest bezpieczny?
Tak bezpieczny, jak go zabezpieczysz. Minimum to HTTPS i sekret w nagłówku. Lepiej: podpis HMAC, sprawdzanie znacznika czasu (ochrona przed powtórzeniem) i ograniczenie rozmiaru danych.
Jak przetestować webhook bez publicznego serwera?
Wyślij zapytanie z własnego skryptu na 127.0.0.1 (jak w tym tekście) albo użyj tunelu, który przekieruje publiczny adres na Twój komputer.
Co się stanie, jeśli mój serwer będzie wyłączony?
Zależy od nadawcy. Stripe czy GitHub ponawiają próby przez jakiś czas. Prosty formularz może zgłoszenie zgubić. Dlatego serwer webhooka powinien odpowiadać szybko, a cięższą pracę (np. wołanie modelu) przy dużym ruchu robić w tle.
Czy mogę połączyć webhook z agentem AI?
Tak, to typowy sposób uruchamiania agentów: zdarzenie przychodzi webhookiem, a zamiast jednego zapytania do modelu startuje pętla agenta z narzędziami. Jak ją zbudować, pokazuję w tekście Jak zbudować agenta AI w Pythonie.
Google · Twoje źródłaPromptowy wyżej w Twoim Google - jednym kliknięciemDodaj do preferowanych źródeł →Źródła i data sprawdzenia
- Cenniki: Make, Zapier, n8n (sprawdzone 4.10.2026).
- Dokumentacja FastAPI.
- Test: mój komputer, 4 października 2026, 14:11. Dane w zgłoszeniach są fikcyjne.
Cały tydzień w AI, w jednym mailu
Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.
Zapisz się za darmo →
