IPN fizetési értesítés
Az IPN (Instant Payment Notification) a Számlázz.hu webhookja. Ha egy bizonylat (számla vagy díjbekérő) kifizetett összege megváltozik, például részleges vagy teljes befizetés után, a Számlázz.hu HTTP POST kérést küld a megadott címre. A kérés törzse application/x-www-form-urlencoded formátumú, és a számla számát, a végösszeget és a kifizetett összeget tartalmazza.
Értesítés csak akkor készül, ha a változás a számla kiállítójának oldalán történik, és a fiókban be van állítva az IPN cím. A kassza/ipn modul beolvassa és típusos objektummá alakítja az értesítést, és segít ellenőrizni, hogy a kérés tényleg a Számlázz.hu-tól jött-e.
Beállítás#
- A Számlázz.hu fiókban nyisd meg a Fiók beállítások → Számlázás alapadatok oldalt, és az oldal alján lévő mezőbe írd be a végpontod címét, például
https://webshop.example.hu/api/szamlazz/ipn. - A végpont a sikeres feldolgozás után HTTP 200 státusszal válaszoljon. A válasz törzse nem számít, csak a státuszkód.
- Ha a végpontot IP-cím alapján korlátozod, engedélyezd a küldő IP-címeket.
Next.js route#
A route csak akkor jelöli fizetettnek a rendelést, és csak akkor küld visszaigazolást, ha a rendelés még nem volt fizetett. Így egy ismételten beérkező értesítés nem küld második e-mailt. Ha a feldolgozás hibát dob, a Next.js 500-as státusszal válaszol, és a Számlázz.hu később újraküldi az értesítést.
Az isSzamlazzIp() az x-forwarded-for fejléc jobb szélső címét vizsgálja, azt, amelyet a hozzád legközelebbi proxy látott. Vercelen ez a kliens címe. Több proxy mögött add meg a trustedProxies opciót, a részleteket lent olvashatod.
Más keretrendszerben#
A readIpnNotification() szabványos Request objektumot vár, így Hono, Cloudflare Workers, Bun és Deno alatt is közvetlenül használható:
Ha a törzset már egy middleware beolvasta, a parseIpnNotification() az objektumból dolgozik:
A kassza/ipn exportjai#
| Export | Leírás |
|---|---|
readIpnNotification(request: Request) | Beolvassa a kérést, és Promise<IpnNotification>-t ad. multipart/form-data törzset is kezel, üres törzsnél az URL query paramétereit olvassa. A MAX_IPN_BODY_BYTES (64 KiB) fölötti törzset validation hibával elutasítja. |
parseIpnNotification(input) | Ugyanez már beolvasott adatból: szövegből, URLSearchParams-ból, FormData-ból vagy Record<string, string> objektumból. |
ipnOkResponse() | HTTP 200 Response, OK törzzsel. |
isSzamlazzIp(ip: string | null | undefined, options?) | true, ha a cím a Számlázz.hu egyik küldő címe. Az options.trustedProxies a jobbról átugrandó megbízható proxyk száma (alapérték: 0), az options.allowedIps a küldő címek egyedi listája. |
SZAMLAZZ_OUTBOUND_IPS | A küldő IP-címek listája. |
IPN_FIELDS | Az IpnNotification mezőneveit a Számlázz.hu paraméterneveire képezi. |
parseIpnAmount(value: string) | Egy IPN összeget alakít számmá, értelmezhetetlen szövegnél undefined. |
IpnNotification#
| Mező | Típus | IPN paraméter | Leírás |
|---|---|---|---|
invoiceNumber | string | szlahu_szamlaszam | Kötelező. A számla száma. |
proformaNumber | string | undefined | szlahu_dijbekero_szama | A díjbekérő száma, ha a számla díjbekérőből készült. |
orderNumber | string | undefined | szlahu_rendelesszam | A rendelésszám, ha szerepelt a számlán. |
grossTotal | number | szlahu_bruttovegosszeg | Kötelező. A számla bruttó végösszege. |
paidAmount | number | szlahu_kifizetettbrutto | Kötelező. A kifizetett bruttó összeg. |
paymentMethod | string | undefined | szlahu_fizetesmod | A fizetés módja, például 'átutalás' vagy 'bankkártya'. |
paymentDate | string | undefined | szlahu_kifizdat | A kifizetés dátuma 'YYYY-MM-DD' alakban, ha be van kapcsolva. |
isFullyPaid | boolean | számolt | true, ha a kifizetett összeg eléri a végösszeget. |
raw | Readonly<Record<string, string>> | minden paraméter | Az összes kapott paraméter szövegként, a fel nem dolgozottak is. |
Az üres opcionális mező undefined lesz. Ha egy kötelező mező hiányzik, vagy egy összeg nem értelmezhető, a kassza validation kategóriájú SzamlazzError-t dob. Az összegeknél a szóközös ezres tagolást ('19 470'), a tizedesvesszőt ('12 700,50') és a tizedespontot ('12700.5') is kezeli.
Az isFullyPaid a két összeg abszolút értékét hasonlítja össze, 0,005-ös tűréssel:
Így a túlfizetés is teljes kifizetésnek számít, és negatív végösszegnél, például helyesbítő számlánál is helyes az eredmény. Részfizetésnél false, a hátralék ilyenkor grossTotal - paidAmount.
Kipróbálás#
A példa egy értesítést állít össze Request objektumként, ellenőrzi a küldő címét, beolvassa az értesítést, és kiírja a kapott IpnNotification objektumot és a 200-as választ.
IPN fizetési értesítés
Webhook kérés feldolgozása és a Számlázz.hu IP-címének ellenőrzése.
Újraküldés és sorrend#
A Számlázz.hu nem azonnal küldi ki az értesítéseket, hanem sorba teszi őket:
- Ha nem 200-as válasz jön, vagy a kérés időtúllépéssel ér véget, 3 percenként újrapróbálja, legfeljebb 10-szer. Utána az értesítést eldobja.
- Nagy forgalomnál az értesítések felgyűlhetnek a küldési sorban, és csak később érkeznek meg.
- Számlánként egyszerre csak egy értesítés vár kiküldésre. Ha közben újabb változás történik, csak a legfrissebb megy ki, a korábbiak törlődnek.
Ebből három szabály következik:
- Ne add össze az értesítéseket. Egy részfizetésről lehet, hogy soha nem kapsz külön értesítést. Mindig a legutóbbi
paidAmountésgrossTotalalapján dönts, vagy azisFullyPaidmezőt használd. - Válaszolj gyorsan. A lassú munkát (e-mail, raktári rendelés) tedd háttérfeladatba, és azonnal küldd a 200-as választ. Egy lassú válasz után ugyanaz az értesítés újra megérkezhet.
- Legyen tartalék. Ha a végpontod 10 próbálkozásnál tovább nem volt elérhető, az értesítés elveszett. Ilyenkor a nyitott rendelések számláit kérdezd le a Számla adatainak lekérése művelettel.
Idempotens feldolgozás#
Ugyanaz az értesítés többször is megérkezhet, ezért a feldolgozásnak ismételve is ugyanazt az eredményt kell adnia. A gyakorlatban:
- Az állapotot feltételesen frissítsd (a rendelés csak akkor legyen fizetett, ha még nem az), és a mellékhatásokat (visszaigazoló e-mail, szállítás indítása) csak akkor indítsd, ha a frissítés tényleg változtatott valamit, ahogy a fenti route teszi.
- A rendelést a
invoiceNumbervagy azorderNumberalapján keresd, ne az értesítés beérkezési sorrendje alapján. - Ha a számlára a saját kódod is rögzít befizetést a
registerPayment()metódussal, az is a kiállító oldali változás, ezért értesítést válthat ki. Az IPN kezelőből ne rögzíts újra befizetést ugyanarra a számlára.
Küldő IP-címek#
A Számlázz.hu az értesítéseket 2025. augusztus 1. óta az alábbi címekről küldi. A korábbi címeket (18.153.1.171, 3.73.114.72, 52.59.28.5) már nem használja, ha még szerepelnek a tűzfalszabályaid között, cseréld le őket.
| Kimenő IP-cím | Mire használd |
|---|---|
| 3.73.214.98 | IPN értesítés és egyéb, a Számlázz.hu felől érkező hívás engedélyezése |
| 3.76.149.232 | IPN értesítés és egyéb, a Számlázz.hu felől érkező hívás engedélyezése |
| 18.153.156.51 | IPN értesítés és egyéb, a Számlázz.hu felől érkező hívás engedélyezése |
Az isSzamlazzIp() a vizsgálat előtt egységes alakra hozza a címet: vesszővel elválasztott listából a jobb szélső elemet veszi (a trustedProxies számú megbízható proxy címét átugorva), levágja a szóközöket és a portot, és kezeli a [...]:port alakot és az IPv4-et IPv6-ba ágyazó ::ffff: előtagot.
Az x-forwarded-for fejléc#
| Környezet | Honnan olvasd a kliens címét? |
|---|---|
| Vercel | x-forwarded-for vagy x-real-ip: a Vercel felülírja őket, az alapbeállítás megfelel. |
| Cloudflare (Workers vagy proxy) | cf-connecting-ip. Az x-forwarded-for jobb szélső eleme is a kliens címe, ha Cloudflare után nincs másik proxy. |
| Saját nginx | A $proxy_add_x_forwarded_for a kliens címét fűzi a fejléc végére, ezt olvassa az alapbeállítás. Ha nginx előtt is van proxy (például load balancer), add meg { trustedProxies: 1 }-et. |
| Express | app.set('trust proxy', ...) beállítás után a req.ip, a proxyk számának megfelelő értékkel. |
A Számlázz.hu dokumentációja az értesítéshez nem ír le aláírást, így a küldő címén kívül nincs mivel ellenőrizned a hitelességét. Ha egy értesítés alapján nagy értékű műveletet indítasz (például kiszállítást), előtte kérdezd le a számlát a kassza.invoices.get() metódussal, és a payments lista alapján dönts.
A kimenő és bejövő forgalom többi szabályát a Hálózat és biztonság oldal foglalja össze.