· NGSRV Team

How to test webhooks locally without deploying

ngsrv creates a public preview link for your local service. The single biggest place that matters is webhook testing — instead of deploying to staging every time you tweak a handler, you forward the real provider's webhook straight to your laptop and debug with breakpoints.

ngsrv helps developers expose local services, share preview links, test webhooks, and get feedback faster. This is the long version of the webhook story.

What "testing webhooks locally" actually requires

A webhook is just an HTTP POST from a provider (Stripe, GitHub, Shopify, Slack, Twilio, etc.) to a URL you gave them. To run that POST against your local code, you need:

  1. A public HTTPS URL the provider can reach (no provider hits localhost).
  2. A stable URL so you don't re-paste it after every restart.
  3. Optional auth to keep randoms out.
  4. Logs you can actually read.

ngsrv ships IP allowlist, header auth, rate limits, and more on every plan. Here's the full setup.

Step 1 — Run your webhook handler locally

Pick a port. We'll use 4242 because that's the Stripe CLI's default forwarding port.

# Node / Next.js API route
npm run dev

# Python / FastAPI
uvicorn main:app --port 4242

# Go
go run ./cmd/server  # set PORT=4242

Make sure the handler logs requests so you can sanity-check.

Step 2 — Start the tunnel with a reserved subdomain

ngsrv http 4242 --subdomain hooks
# -> https://hooks.tnl.ngsrv.com

Reserved subdomains stay yours between sessions on Pro and above. This is the part that matters: Stripe / GitHub / Shopify webhook URLs hate changing, so you set this once and keep it for the life of the project.

Step 3 — Point providers at the URL

Stripe

In the Stripe dashboard:

  • Developers → Webhooks → Add endpoint
  • URL: https://hooks.tnl.ngsrv.com/webhook (or whatever path your handler uses)
  • Events: pick the ones you care about (checkout.session.completed, invoice.paid, …)

Stripe will send a test event. Watch it land in your handler.

GitHub

For a repo:

  • Settings → Webhooks → Add webhook
  • Payload URL: https://hooks.tnl.ngsrv.com/github
  • Content type: application/json
  • Secret: (set one — see below)
  • Pick "Just the push event" or "Send me everything" while developing.

Shopify, Slack, Twilio, Discord

All the same shape. URL goes into the provider's webhook configuration.

Step 4 — Verify signatures

Real webhook handlers should verify signatures. ngsrv passes the original request headers through untouched, so:

  • Stripe: verify Stripe-Signature against your endpoint secret.
  • GitHub: verify X-Hub-Signature-256 against the webhook secret.
  • Slack: verify X-Slack-Signature against your signing secret.

None of this changes because of ngsrv. The bytes the provider sent are the bytes your handler receives.

Step 5 — Filter who can reach the URL

While developing, restrict the endpoint using policies you created in the dashboard. Build an IP allowlist policy with Stripe's published ranges under IP security, then attach it by ID:

tunnels:
  - name: webhooks
    port: 4242
    subdomain: hooks
    security_policies:
      - ngsrv_ips_stripe

You can stack multiple policy IDs if needed. Stripe signature verification still runs in your app; dashboard policies only gate who can reach the tunnel at the edge.

Step 6 — Read the logs

ngsrv emits structured JSON access logs:

{
  "ts": "2026-05-18T19:42:11Z",
  "tunnel": "webhooks",
  "method": "POST",
  "path": "/webhook",
  "status": 200,
  "duration_ms": 42,
  "req_bytes": 1822,
  "headers": {
    "stripe-signature": "t=...,v1=..."
  }
}

Pipe to jq for tidy output:

ngsrv run | jq -r 'select(.tunnel == "webhooks") | "\(.status) \(.method) \(.path) \(.duration_ms)ms"'

This is one of the actual operational reasons to prefer a tunneling tool with stable JSON logs.

What this saves you

Deploy-to-staging loopngsrv + local handler
Time per change5–15 minutes (CI + deploy)Hot-reload (seconds)
DebuggerNoneFull IDE breakpoints
Inspecting request bodyRead prod logsRead in your own process
CostStaging server + CI minutesFree tier of ngsrv