Stripe webhooky v PHP: spolehlivý handler, kterému můžete věřit
Webhook Stripe, který funguje na vašem notebooku, ještě není webhook, kterému můžete důvěřovat penězi. Tato příručka ukazuje, jak ověřovat podpisy, rychle potvrzovat, deduplikovat události a zpracovávat opakované pokusy v prostém PHP a Laravel s kódem, který můžete vložit do projektu.
Ve zkratce
- Za zdroj pravdy o stavu platby považujte webhook, nikoli přesměrování prohlížeče.
- Než se dotknete užitečného obsahu, přečtěte si nezpracované tělo požadavku a ověřte záhlaví
Stripe-Signature. - Rychle vraťte odpověď 2xx a proveďte skutečnou práci ve frontě nebo po uložení události.
- Uložte ID události s jedinečným indexem: Stripe může doručit stejnou událost více než jednou a mimo pořadí.
- Před spuštěním otestujte pomocí Stripe CLI (
stripe listenastripe trigger).
Pokud přijímáte platby pomocí Stripe, webhooky Stripe v PHP jsou tou částí, která rozhoduje o tom, zda jsou objednávky označeny jako zaplacené, aktivují se předplatná a refundace se promítnou do vaší databáze. Stránka pokladny je pouze vstupními dveřmi: zákazníci zavírají karty, banky potvrzují pozdě a některé platební metody se vyrovnávají o minuty nebo dny později. Webhook je zpráva od Stripe, která říká, co se skutečně stalo.
Vytváříme a udržujeme integraci plateb pro klienty a pro naše vlastní produkty (například Bill, který přijímá platby Stripe a PayPal na veřejné platební stránce). Tento článek je kontrolní seznam, kterým se řídíme, když píšeme koncový bod webhooku Stripe v PHP.
Jak webhooky Stripe fungují za jednu minutu
Zaregistrujete URL HTTPS v Stripe Dashboard (nebo prostřednictvím API) a zvolíte, které typy událostí chcete přijímat. Když se něco stane – dokončí se relace Checkout, zaplatí se faktura, vrátí se poplatek – Stripe odešle požadavek POST s objektem události JSON na tuto adresu URL. Váš server musí odpovědět stavovým kódem 2xx. Pokud se tak nestane, Stripe to zopakuje s exponenciálním stažením po dobu až tří dnů v živém režimu.
Celý design formují tři skutečnosti:
- Události mohou přijít vícekrát. Opakované pokusy a problémy se sítí dělají duplikáty normální.
- Události mohou přijít mimo pořadí. Může se zobrazit
invoice.paidpředcustomer.subscription.created. - Na vaši adresu URL může POSTÁVAT kdokoli. Bez ověření podpisu může útočník označit svou vlastní objednávku jako zaplacenou.
Krok 1. Nainstalujte SDK a vytvořte koncový bod
Použijte oficiální knihovnu stripe/stripe-php a udržujte koncový bod v tajnosti (začíná whsec_) v proměnné prostředí, nikdy v úložišti.
composer require stripe/stripe-php
# .env
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
Každý koncový bod webhooku v Dashboardu má své vlastní podpisové tajemství a tajemství z stripe listen ve vývoji je opět jiné. Velmi běžnou chybou „ověření podpisu se nezdařilo“ je jednoduše použití nesprávného.
Krok 2. Ověřte podpis na surovém těle
Stripe podepíše každou žádost. Záhlaví Stripe-Signature obsahuje časové razítko a jeden nebo více podpisů vypočítaných jako HMAC-SHA256 z timestamp.payload s vaším tajemstvím koncového bodu. Knihovna kontroluje podpis a standardně odmítá události starší než pět minut.
Důležitý detail: ověření vyžaduje přesné nezpracované bajty odeslané Stripe. Pokud framework analyzuje JSON a vy jej znovu zakódujete, mezery se změní a podpis se již neshoduje.
<?php
// public/webhooks/stripe.php (plain PHP)
require __DIR__ . '/../../vendor/autoload.php';
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
$secret = getenv('STRIPE_WEBHOOK_SECRET');
try {
$event = \Stripe\Webhook::constructEvent($payload, $sigHeader, $secret);
} catch (\UnexpectedValueException $e) {
http_response_code(400); // invalid JSON
exit;
} catch (\Stripe\Exception\SignatureVerificationException $e) {
http_response_code(400); // bad or missing signature
exit;
}
V Laravel čtěte $request->getContent() místo $request->all() a vylučte cestu z ochrany CSRF (webhooky nenesou token CSRF, ochrana je podpis). Pokud můžete, umístěte trasu mimo skupinu middlewaru web relace.
Krok 3. Udělejte psovoda idempotentním
Idempotentní znamená: zpracování stejné události dvakrát dává stejný výsledek jako zpracování jednou. Nejjednodušším spolehlivým přístupem je tabulka zpracovaných ID událostí s jedinečným indexem. Nejprve vložte; pokud se vložení nezdaří, protože ID existuje, událost jste již viděli.
CREATE TABLE stripe_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
event_id VARCHAR(64) NOT NULL,
type VARCHAR(100) NOT NULL,
payload JSON NOT NULL,
status ENUM('received','processed','failed') NOT NULL DEFAULT 'received',
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL,
UNIQUE KEY uniq_event (event_id)
);
try {
$pdo->prepare('INSERT INTO stripe_events (event_id, type, payload) VALUES (?, ?, ?)')
->execute([$event->id, $event->type, $payload]);
} catch (PDOException $e) {
if ($e->getCode() === '23000') { // duplicate key: already received
http_response_code(200);
exit;
}
throw $e;
}
Uložením nezpracovaného užitečného zatížení získáte auditní stopu: když se zákazník zeptá „proč mi bylo účtováno dvakrát?“, můžete si přehrát přesně to, co vám Stripe řekl. Umožňuje také znovu zpracovat neúspěšné události poté, co opravíte chybu.
Krok 4. Potvrďte rychle, zpracujte samostatně
Stripe očekává odpověď během několika sekund. Pokud váš handler posílá e-maily, volá jiná rozhraní API nebo generuje soubory PDF, vyprší časový limit, Stripe se pokusí znovu a vy budete dělat stejnou práci znovu. Udělejte v požadavku minimum – ověřte, uložte, odpovězte 200 – a spusťte skutečnou logiku ve frontovém pracovníkovi ( fronty Laravel, Symfony Messenger, status = 'received' nebo pracovník spravovaný supervizorem).
Pokud událost nelze vůbec uložit (databáze nefunguje), vraťte stav 5xx. To je jediný případ, kdy chcete, aby Stripe opakoval později.
Krok 5. Zvládněte události, na kterých záleží
Nepotřebujete každý typ události. Přihlaste se pouze k tomu, co vaše obchodní logika používá; méně odběrů znamená méně hluku a menší útočnou plochu. Typická sada:
checkout.session.completed- zákazník dokončil Checkout. Vyhledejte svou objednávku pomocíclient_reference_idnebo metadat, která jste nastavili při vytváření relace.payment_intent.succeededapayment_intent.payment_failed– pro vlastní platební toky.invoice.paidainvoice.payment_failed- fakturace předplatného.customer.subscription.updatedacustomer.subscription.deleted- změny a zrušení plánu.charge.refundedandcharge.dispute.created– peníze se vrací.
switch ($event->type) {
case 'checkout.session.completed':
$session = $event->data->object;
$orderId = $session->client_reference_id;
if ($session->payment_status === 'paid') {
markOrderPaid($orderId, $session->payment_intent);
}
break;
case 'invoice.payment_failed':
notifyBillingProblem($event->data->object->customer);
break;
case 'charge.refunded':
markOrderRefunded($event->data->object->payment_intent);
break;
default:
// unknown or unsubscribed type: acknowledge and ignore
break;
}
Dva zvyky zabraňují většině chyb. Za prvé, nikdy nevěřte částkám z prohlížeče: porovnejte zaplacenou částku a měnu v akci s objednávkou ve vaší databázi. Za druhé, protože události mohou být mimo pořadí, udělejte změny stavu monotónní: nepřesouvejte objednávku z „zaplaceno“ zpět do „nevyřízené“, protože starší událost dorazila pozdě. Pokud si nejste jisti, načtěte aktuální objekt z rozhraní API namísto spoléhání se na snímek události.
Krok 6. Otestujte lokálně pomocí Stripe CLI
Stripe CLI předává skutečné události testovacího režimu do vašeho počítače, takže nepotřebujete veřejný tunel:
stripe login
stripe listen --forward-to http://localhost:8000/webhooks/stripe
# in another terminal:
stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed
stripe listen vytiskne dočasné tajemství whsec_. Vložte to do svého místního .env. Záměrně také otestujte nešťastné cesty: odešlete požadavek se špatným podpisem (očekávejte 400), stejnou událost odešlete dvakrát (očekávejte jeden databázový efekt) a zastavte na minutu svého pracovníka fronty, abyste viděli, že se události místo toho, aby zmizely, hromadí.
Časté chyby, které vidíme při auditech
- Použití přesměrování jako dokladu o platbě. Stránku
success_urlmůže otevřít kdokoli; zobrazovat „potvrzuji platbu“, dokud webhook nedorazí. - Analýza JSON před ověřením. Znovu zakódovaná těla poruší podpis; Častým viníkem jsou analyzátory těl kostry.
- Jedno tajemství na zkoušku a naživo. Testovací a živý režim mají samostatné klávesy a samostatné koncové body webhooku.
- Žádné protokolování. Bez uložených událostí a viditelného kanálu selhání zákazníci zjistí chyby plateb.
- Pomalé manipulátory. Cokoli za několik sekund spustí opakování a duplicitní práci.
- Blocking Stripe na firewallu nebo WAF. Pokud používáte Cloudflare nebo jiný WAF, povolte cestu webhooku a zkontrolujte, zda pravidla botů nevrací 403 na servery Stripe.
Monitorování: jak víte, že funguje
Webhook se může zlomit měsíce po spuštění: nasazená změna přejmenuje sloupec, tajemství se otočí, certifikát vyprší. Panel Stripe Dashboard zobrazuje pokusy o doručení a selhání pro každý koncový bod a Stripe vám pošle e-mail, když koncový bod stále selhává. Buďte na své straně upozorněni na tři věci: rostoucí počet událostí v received nebo failed, náhlý pokles událostí za hodinu a jakoukoli rychlost 4xx nebo 5xx na trase webhooku. Zbytek zachytí malá každodenní práce na odsouhlasení, která porovná zaplacené objednávky ve vaší databázi se seznamem úspěšných plateb Stripe.
Jak můžeme pomoci
Navrhujeme a vytváříme produkční integrace plateb a webhooků s kontrolami podpisů, idempotence, opakováními a protokoly, které může váš tým číst. Pokud již máte tok Stripe a chcete druhý pár očí, revize architektury nebo konzultace jsou nejrychlejším způsobem, jak najít slabá místa. Chcete-li získat širší obrázek o přesunu kritických událostí ze Zapier nebo Make, přečtěte si, když se žádný kód nezlomí, a pro dalšího poskytovatele si přečtěte našeho průvodce integrací WayForPay v PHP.
Časté otázky
Proč se ověření podpisu webhooku Stripe nezdaří?
Obvyklými příčinami je použití nesprávného tajného podpisu (test vs živé, CLI vs koncový bod Dashboard), ověření analyzovaného nebo znovu zakódovaného těla namísto nezpracovaného těla požadavku nebo proxy, který upravuje datovou část. Ověřte proti přesným nezpracovaným bajtům pomocí vlastního tajemství whsec_ koncového bodu.
Jak zabráním zpracování události Stripe dvakrát?
Uložte každé ID události do tabulky s jedinečným indexem a vložte jej před provedením jakékoli práce. Pokud se vložení nezdaří s duplicitním klíčem, vraťte 200 a přeskočte zpracování. Rovněž zajistěte, aby bylo opakování samotné obchodní akce bezpečné.
Jaký kód odpovědi by měl webhook Stripe vrátit?
Vraťte kód 2xx (200 nebo 204), jakmile bude událost ověřena a uložena. Vraťte 400 pro neplatný podpis a 5xx pouze v případě, že jste nemohli uložit událost a chcete, aby Stripe opakoval.
Jak dlouho Stripe opakuje neúspěšné webhooky?
V živém režimu se Stripe opakuje s exponenciálním stažením po dobu až tří dnů. V testovacím režimu je okno opakování mnohem kratší, takže se při posuzování produkčního chování nespoléhejte na opakování v testovacím režimu.
Mohu testovat webhooky Stripe bez veřejné adresy URL?
Ano. Spusťte stripe listen --forward-to pomocí Stripe CLI pro předání testovacích událostí na localhost a použijte stripe trigger pro generování ukázkových událostí.
Volání bezplatného zjišťování
Přineste chaotický stack nebo zaseknutou integraci - Integrono namapuje nejchytřejší první krok.
Dostanete seniorský pohled na rozpočtové pasti, závislosti a na to, zda koupit, slepit nebo postavit - než začne placená práce. CEO zůstává v okruhu.