Od localhost do internetu: ngrok w testowaniu webhooków

Jedna komenda daje localhostowi publiczny URL z HTTPS — bez DNS, bez certyfikatu, bez reguł firewalla. Oto jak efektywnie używać ngrok w pracy z Laravel’em: od testowania webhooków Stripe lokalnie, po prezentowanie funkcji klientowi bez deployu.

📋 Spis treści

🔌 Jak działa ngrok

ngrok tworzy bezpieczny tunel między publicznym URL-em a portem działającym na lokalnej maszynie. Gdy żądanie trafia na https://abc123.ngrok-free.app, ngrok przekazuje je zaszyfrowanym tunelem do http://localhost:8000 (lub innego skonfigurowanego portu).

Dostawca płatności (Stripe)
        │
        ▼
https://twoja-subdomena.ngrok-free.app   ← publiczny endpoint HTTPS
        │  (zaszyfrowany tunel)
        ▼
http://localhost:8000                    ← lokalny serwer Laravel
        │
        ▼
  WebhookController@handle

Bez konfiguracji DNS, bez certyfikatu SSL, bez reguł firewalla. Tunel jest nawiązywany wychodzącym połączeniem z Twojej maszyny — działa za NAT-em, korporacyjnym firewallem i VPN-em.

Czym ngrok nie jest: narzędziem do deploymentu. Tunel zamyka się po zamknięciu terminala. To wyłącznie narzędzie deweloperskie i debugujące.

⚙️ Instalacja i pierwsze uruchomienie

macOS (Homebrew):

brew install ngrok/ngrok/ngrok

Bezpośrednie pobranie:

# Pobierz z https://ngrok.com/download — rozpakuj i przenieś do PATH
sudo mv ngrok /usr/local/bin

Uwierzytelnienie (wymagane darmowe konto dla stabilnych URL-i):

ngrok config add-authtoken TWÓJ_TOKEN

Wystawienie lokalnego serwera Laravel:

# Uruchom Laravel
php artisan serve  # domyślnie port :8000

# W drugim terminalu wyeksponuj go na zewnątrz
ngrok http 8000

Wynik:

Session Status    online
Account           [email protected] (Plan: Free)
Forwarding        https://abc123.ngrok-free.app -> http://localhost:8000

Connections       ttl     opn     rt1     rt5     p50     p90
                  0       0       0.00    0.00    0.00    0.00

URL https://abc123.ngrok-free.app jest teraz publicznie dostępny. Możesz go wkleić gdziekolwiek — do panelu Stripe, Postmana, przeglądarki kolegi, urządzenia mobilnego w innej sieci.

Z niestandardowym portem (Laravel Sail / Docker):

ngrok http 80   # jeśli Sail mapuje na port 80

🔐 Statyczne domeny i stabilne URL-e

Darmowy plan przydziela losową subdomenę przy każdym restarcie tunelu. To problem przy konfiguracji webhooków — przy każdym restarcie ngrok trzeba by aktualizować URL w panelu Stripe.

Rozwiązanie: statyczna domena (bezpłatna)

Darmowy plan ngrok zawiera jedną statyczną domenę. Zarezerwuj ją na dashboard.ngrok.com/domains.

ngrok http --domain=twoja-nazwa.ngrok-free.app 8000

Teraz URL się nie zmienia. Skonfiguruj go w Stripe raz i restartuj ngrok ile razy chcesz.

Plik konfiguracyjny dla powtarzalnych sesji:

# ~/.config/ngrok/ngrok.yml

version: "3"
authtoken: TWÓJ_TOKEN

tunnels:
  laravel:
    proto: http
    addr: 8000
    domain: twoja-nazwa.ngrok-free.app
ngrok start laravel

💳 Testowanie webhooków płatności lokalnie

Tu ngrok przynosi największą wartość. Dostawcy płatności wysyłają zdarzenia webhook (płatność potwierdzona, zwrot, anulowanie subskrypcji) na URL zarejestrowany w ich panelu. Ten URL musi być publicznie dostępny. Podczas lokalnego developmentu localhost odpada.

Stripe

1. Uruchom ngrok ze statyczną domeną:

ngrok http --domain=twoja-nazwa.ngrok-free.app 8000

2. Zarejestruj endpoint webhooka w panelu Stripe:

Developers → Webhooks → Add endpoint
URL: https://twoja-nazwa.ngrok-free.app/webhook/stripe
Events: payment_intent.succeeded, payment_intent.payment_failed, charge.refunded

3. Trasa webhooka w Laravel (poza middleware api, bez CSRF):

// routes/web.php

use App\Http\Controllers\Webhook\StripeWebhookController;

Route::post('/webhook/stripe', StripeWebhookController::class)
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);

4. Kontroler — zawsze weryfikuj sygnaturę:

// app/Http/Controllers/Webhook/StripeWebhookController.php

<?php

declare(strict_types=1);

namespace App\Http\Controllers\Webhook;

use App\Http\Controllers\Controller;
use App\Jobs\HandleStripePaymentSucceeded;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Webhook;

class StripeWebhookController extends Controller
{
    public function __invoke(Request $request): Response
    {
        $payload   = $request->getContent();
        $sigHeader = $request->header('Stripe-Signature');

        try {
            $event = Webhook::constructEvent(
                $payload,
                $sigHeader,
                config('services.stripe.webhook_secret'),
            );
        } catch (SignatureVerificationException) {
            return response('Invalid signature', 400);
        }

        match ($event->type) {
            'payment_intent.succeeded'      => HandleStripePaymentSucceeded::dispatch($event->data->object),
            'payment_intent.payment_failed' => logger()->warning('Payment failed', ['id' => $event->data->object->id]),
            default                         => null,
        };

        return response('OK', 200);
    }
}

5. Ustaw sekret webhooka w .env:

# Z panelu Stripe → Webhooks → Twój endpoint → Signing secret
STRIPE_WEBHOOK_SECRET=whsec_...

6. Wywołaj testowe zdarzenie przez Stripe CLI:

brew install stripe/stripe-cli/stripe
stripe login
stripe trigger payment_intent.succeeded

Stripe wysyła prawdziwy podpisany webhook na URL ngrok, który przekazuje go do Laravel. Możesz debugować cały przepływ przez dd() lub Telescope — bez żadnego mockowania.

Przelewy24 / PayU / Tpay

Polscy dostawcy płatności działają identycznie, ale zamiast nagłówkowej sygnatury Stripe używają whitelisty IP lub podpisów HMAC. ngrok działa tak samo.

Przykład Przelewy24:

Zarejestruj URL ngrok jako adres powiadomień zwrotnych w panelu P24:

https://twoja-nazwa.ngrok-free.app/webhook/p24
// app/Http/Controllers/Webhook/P24WebhookController.php

<?php

declare(strict_types=1);

namespace App\Http\Controllers\Webhook;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;

class P24WebhookController extends Controller
{
    public function __invoke(Request $request): Response
    {
        // P24 wysyła weryfikację CRC — sprawdź ją przed przetworzeniem
        $expectedSign = md5(
            $request->input('p24_session_id') . '|' .
            $request->input('p24_order_id')   . '|' .
            $request->input('p24_amount')     . '|' .
            $request->input('p24_currency')   . '|' .
            config('services.p24.crc_key')
        );

        if ($request->input('p24_sign') !== $expectedSign) {
            Log::warning('P24 nieprawidłowa sygnatura', $request->all());

            return response('ERROR', 400);
        }

        // Przetwórz zweryfikowane powiadomienie
        Log::info('P24 płatność potwierdzona', [
            'order_id'   => $request->input('p24_order_id'),
            'session_id' => $request->input('p24_session_id'),
            'amount'     => $request->input('p24_amount'),
        ]);

        return response('OK', 200);
    }
}

Ważne: Zawsze weryfikuj sygnaturę lub CRC każdego przychodzącego webhooka. ngrok wystawia lokalny serwer na publiczny internet — niezweryfikowany handler webhooka to otwarty endpoint.

🛠️ ngrok z Laravel Herd i Valet

Jeśli używasz Laravel Herd lub Valet, Twoja aplikacja jest już dostępna pod domeną .test (np. myapp.test). ngrok może tunelować bezpośrednio do tego wirtualnego hosta.

Herd / Valet:

# Herd używa portu 80 z wirtualnym hostingiem — przekaż nagłówek host
ngrok http --host-header=myapp.test 80

Bez --host-header żądanie dociera do nginx/caddy bez poprawnego nagłówka Host i zwraca 404.

Albo użyj wbudowanej funkcji share w Herd:

Laravel Herd Pro zawiera wbudowaną funkcję udostępniania stron, która używa ngrok pod spodem. Jeśli masz Herd Pro, kliknij prawym przyciskiem myszy na stronę w aplikacji Herd w pasku menu i wybierz "Share" — konfiguracja ngrok jest obsługiwana automatycznie.

Dla Herd Basic używaj ngrok bezpośrednio z flagą --host-header.

🔍 Inspektor ngrok — podgląd surowych payloadów webhooków

ngrok uruchamia lokalny inspektor webowy na http://localhost:4040. To jedna z jego najbardziej przydatnych funkcji przy debugowaniu webhooków.

Otwórz przeglądarkę podczas działania ngrok:

http://localhost:4040

Otrzymasz podgląd w czasie rzeczywistym każdego żądania, które przeszło przez tunel:

  • Pełne nagłówki i treść żądania (surowy payload JSON od Stripe, P24 itp.)
  • Status odpowiedzi i treść zwrócona przez Laravel
  • Czas obsługi żądania
  • Przycisk Replay — wysyła dokładnie to samo żądanie ponownie bez wywoływania go od nowa po stronie dostawcy płatności

Funkcja replay jest szczególnie przydatna przy iteracji nad logiką handlera webhooka. Wywołaj webhook raz ze Stripe, a potem odtwarzaj go z inspektora ile razy potrzebujesz, poprawiając kod. Nie trzeba wielokrotnie tworzyć prawdziwych transakcji testowych.

Dostęp do inspektora przez API:

# Wylistuj ostatnie żądania programowo
curl http://localhost:4040/api/requests/http | jq '.requests[0].response.status'

🧪 Automatyczne testowanie webhooków bez ngrok

ngrok to właściwe narzędzie do interaktywnego debugowania. W testach automatycznych używaj wbudowanego testowania HTTP w Laravel.

// tests/Feature/Webhook/StripeWebhookTest.php

<?php

declare(strict_types=1);

use App\Models\Order;
use Illuminate\Support\Facades\Queue;
use App\Jobs\HandleStripePaymentSucceeded;

it('przetwarza prawidłowy webhook payment_intent.succeeded od Stripe', function () {
    Queue::fake();

    $order = Order::factory()->create(['status' => 'pending']);

    $payload = json_encode([
        'type' => 'payment_intent.succeeded',
        'data' => [
            'object' => [
                'id'       => 'pi_test_123',
                'metadata' => ['order_id' => $order->id],
                'amount'   => 9900,
                'currency' => 'pln',
            ],
        ],
    ]);

    // Buduj prawidłową sygnaturę Stripe dla testu
    $secret    = config('services.stripe.webhook_secret');
    $timestamp = time();
    $signature = hash_hmac('sha256', "{$timestamp}.{$payload}", $secret);
    $header    = "t={$timestamp},v1={$signature}";

    $this->postJson('/webhook/stripe', json_decode($payload, true), [
        'Stripe-Signature' => $header,
    ])->assertOk();

    Queue::assertDispatched(HandleStripePaymentSucceeded::class);
});

it('odrzuca webhook z nieprawidłową sygnaturą', function () {
    $this->postJson('/webhook/stripe', ['type' => 'payment_intent.succeeded'], [
        'Stripe-Signature' => 't=000,v1=invalidsig',
    ])->assertStatus(400);
});

Ten test działa bez ngrok, bez dostępu do sieci, bez prawdziwego konta Stripe. Używaj ngrok za pierwszym razem, gdy integrujesz nowy webhook — gdy już rozumiesz strukturę payloadu, napisz test i nie musisz już tunelu do regresji.

🚀 Inne przypadki użycia, które warto znać

Aplikacje mobilne: React Native lub Flutter muszą trafić do prawdziwego API. http://localhost:8000 nie działa na fizycznym urządzeniu ani symulatorze w innej sieci. Wskaż aplikację na URL ngrok.

Callbacki OAuth: Dostawcy OAuth (GitHub, Google) wymagają zarejestrowanego URI przekierowania. Podczas developmentu zarejestruj domenę ngrok jako adres callbacka i zaktualizuj go raz przy konfiguracji statycznej domeny.

Demonstracja funkcji klientowi bez deployu: Uruchom ngrok, wyślij link, zaprezentuj funkcję. URL działa dla każdego.

Testowanie funkcji wymagających HTTPS lokalnie: Niektóre API przeglądarki (geolokalizacja, kamera, Service Workers) wymagają HTTPS. ngrok zapewnia HTTPS od razu, nawet gdy lokalny serwer działa na HTTP.

Inspekcja dowolnego ruchu HTTP: ngrok działa z każdym lokalnym serwerem — nie tylko PHP. Używaj go z python -m http.server, Node.js lub innym lokalnym serwisem.

✅ Podsumowanie

  • ngrok tworzy zaszyfrowany publiczny tunel do lokalnego serwera jedną komendą — bez DNS, SSL ani reguł firewalla
  • Używaj statycznej domeny (bezpłatnej), żeby URL-e webhooków nie zmieniały się między restartami tunelu
  • Do webhooków płatności (Stripe, P24, PayU, Tpay) zarejestruj URL ngrok w panelu dostawcy raz i testuj cały przepływ end-to-end lokalnie — łącznie z prawdziwymi sygnaturami
  • Zawsze weryfikuj sygnatury webhooków — ngrok to publiczny endpoint
  • Inspektor ngrok pod http://localhost:4040 pozwala odtwarzać webhooki bez ponownego wywoływania ich od strony dostawcy płatności
  • Do CI i testów regresji mockuj webhook podpisanym payloadem w teście feature — ngrok zostaw do interaktywnej pracy integracyjnej
  • Flaga --host-header jest wymagana przy tunelowaniu do wirtualnych hostów Herd lub Valet

Obserwuj mnie na LinkedIn po więcej wskazówek o Laravel! Straciłeś kiedyś godziny debugując webhook, który działał tylko na produkcji? ngrok by to zaoszczędził. Daj znać w komentarzach!

Komentarze (0)
Zostaw komentarz

© 2026 Wszelkie prawa zastrzeżone.