Webhook Inspector

Every request that arrives at a tunnel URL is captured to disk: method, path, headers, body, status, timing. The inspector lets you browse the log, filter it, and replay any request back at your local server. Free on every plan.

Why this matters

Webhook providers don't let you re-fire an event easily. If your handler crashed at 3am on a Stripe invoice.paid event, you'd normally have to wait for the next one — or hand-craft a curl command from the provider's docs. The inspector skips both: the original request body, headers, and signature are already on disk, and lpm tunnel replay <id> re-delivers them verbatim.

Browse captured events

lpm tunnel inspect                         # terminal table view
lpm tunnel inspect --ui                    # open browser inspector
lpm tunnel log                             # event log browse view (alias: logs)

Each captured event has an id, a session name (if you passed --session), the method/path, the status returned by your local server, and a timestamp.

Filter

lpm tunnel inspect --last 10               # last 10 events
lpm tunnel inspect --status 4xx            # only 4xx responses
lpm tunnel inspect --status 5xx            # only 5xx responses
lpm tunnel inspect --filter '/webhooks/stripe'    # path filter
lpm tunnel inspect --session "stripe-test"

Filters compose — --status 5xx --last 5 is "the five most recent 5xx responses." Combine with --json for piping into other tools.

Replay

lpm tunnel replay 3                        # re-deliver event #3 to localhost
lpm tunnel replay 3 --port 4000            # to a different port than original
lpm tunnel replay 3..7                     # replay a range

Replay posts the original method, path, headers, and body back at the local port. Provider signatures (Stripe-Signature, X-Hub-Signature, etc.) round-trip unchanged, so your existing signature-verification code runs exactly the way it did the first time.

A replay is logged as a new event with a replay:<id> marker — you can replay a replay if needed (rare, but handy when debugging the inspector itself).

Browser inspector

lpm tunnel inspect --ui                    # open in default browser
lpm tunnel inspect --ui --inspect-port 4500    # specific port

The browser UI:

  • Live-updates as new events arrive (server-sent events).
  • Shows request/response bodies with content-type detection (JSON, form, raw).
  • One-click replay against the local port.
  • One-click copy as curl.
  • Filter by path / status / session in the URL bar.

The same UI launches automatically when you start a tunnel unless you pass --no-inspect. The startup banner prints the URL.

Where the data lives

project root/
└── .lpm/
    └── tunnel/
        ├── sessions/
        │   ├── <session-id>/
        │   │   ├── meta.json
        │   │   └── events/
        │   │       ├── 0001.req
        │   │       ├── 0001.res
        │   │       ├── 0002.req
        │   │       └── ...

Bodies are stored verbatim. The directory survives across runs — captures from yesterday's tunnel are still there for replay today, scoped per session.

To clear captures: rm -rf .lpm/tunnel/sessions/. (There's no destructive CLI command for this on purpose — the data is small and useful to keep around.)

Auto-acked events

When --auto-ack is on and the local server is down, the tunnel returns 200 OK and captures the event with an auto-ack marker. The provider sees a successful response and won't deactivate your endpoint; you see the event in the inspector and can replay it once your server is back:

lpm tunnel 3000 --auto-ack
# ... local server crashes, auto-ack fires ...
# ... local server restarts ...
lpm tunnel inspect --status auto-ack       # find the events that never reached your code
lpm tunnel replay <range>                  # deliver them now

Headers and signatures

The inspector keeps the full header set, including provider-specific signing headers (Stripe-Signature, X-Hub-Signature-256, etc.). Replays send them unchanged.

Two things to know about replays:

  • The original Date header replays verbatim. If your signature verifier rejects requests older than N minutes, you'll need to relax that check during development, or your replay will fail verification even though the signature itself is intact.
  • IP-based allow-lists (Stripe's IP range, GitHub's webhook IP range) won't match replays — the replay originates from your machine, not the provider's IP. Don't IP-allow-list in dev.

Session naming

lpm tunnel 3000 --session "stripe-test"
lpm tunnel 3000 --session "github-test"

Sessions partition the capture log. Without --session, every run shares one default session and captures accumulate. With --session, each name gets its own directory and inspector filter — handy when you bounce between webhook setups.

What replays don't do

  • Don't hit the original provider. Replay is local: the request goes from the inspector to your localhost port, never out to the internet.
  • Don't re-emit provider events. If you replay a payment_intent.succeeded, it doesn't make Stripe fire a new one.
  • Don't sign their own requests. The signature is whatever the provider sent originally — replays preserve it but don't regenerate it.

Next