Testing localhost & webhooks from your phone

The most common confusion in mobile API testing, untangled: why localhost fails on a phone, the three ways to fix it, and how webhooks fit in.

The one-line answer

localhost always means "this device" — on your phone it points at the phone. Use your laptop's LAN IP on the same Wi-Fi, Tailscale from anywhere, or a tunnel when the internet must reach you.

Option 1 — Same Wi-Fi: the LAN IP (free, 2 minutes)

Find your laptop's address (macOS: ipconfig getifaddr en0 · Windows: ipconfig · Linux: hostname -I) and call http://192.168.1.42:3000 from the phone instead of localhost:3000. Two gotchas cause 90% of the "still refused" cases: your dev server must listen on all interfaces, not just loopback (vite --host, next dev -H 0.0.0.0, flask run --host=0.0.0.0, php -S 0.0.0.0:8000…), and the OS firewall must allow incoming connections for it. On iOS, accept the Local Network permission when prompted — without it every LAN request fails silently.

Option 2 — Tailscale: your devices, one private network

Install Tailscale on laptop and phone, sign in with the same account, done: each device gets a stable private address (100.x.y.z, or a MagicDNS name like my-macbook). Your phone now reaches the dev server from the couch, the office or cellular — no port forwarding, nothing exposed to the public internet, free for personal use. This is the "it just works every day" option for testing against your own machines.

Option 3 — Tunnels: when the internet must reach you

ngrok http 3000 or cloudflared tunnel --url http://localhost:3000 gives you a public HTTPS URL that forwards to your laptop. This is the tool for receiving webhooks: Stripe, GitHub or Twilio can't call 192.168.1.42 — they need a real URL. Remember the trade-off: while the tunnel is up, that URL is reachable by anyone who has it, so treat it as temporary and rotate it.

Webhooks, both directions

Receiving: tunnel → paste the public URL into the provider's webhook settings → watch requests arrive in your server logs. Sending & replaying: no tunnel needed at all. Take the provider's payload (their docs ship samples), build the POST with the right Content-Type and signature headers, and fire it at your endpoint — from ReqPad this works against a LAN IP, a Tailscale address or a tunnel URL alike, with the request saved to history so replaying a failing webhook is one tap, on the train, without the laptop. The cURL converter and Base64 tool help when the provider docs hand you raw examples.

FAQ

Why can't my phone open localhost:3000?

Because localhost always means "this device". On your phone, localhost:3000 points at the phone itself, where nothing is listening. You need your laptop's LAN IP (192.168.x.x), a private VPN address (Tailscale), or a public tunnel URL (ngrok / cloudflared).

My server is on the right IP but the phone still gets connection refused. Why?

Two usual suspects: the dev server is bound to 127.0.0.1 only (start it with --host 0.0.0.0 or your framework's equivalent), or the laptop firewall is blocking incoming connections. On iOS, also accept the Local Network permission prompt when your API client first touches a LAN address.

Do I need a public URL to test webhooks?

Only for receiving them from third-party services — Stripe or GitHub cannot reach your laptop, so you expose it with ngrok or cloudflared and give them that URL. For sending or replaying webhook payloads against your own endpoint, no tunnel is needed: build the POST with the provider's JSON body and signature headers and send it directly.

Tailscale or ngrok — which one?

Different jobs. Tailscale gives your devices a private network: the phone reaches the laptop from anywhere, nothing is public — ideal for day-to-day dev. Tunnels (ngrok, cloudflared) make a port temporarily public — required when an external service must call you, with the bill that anyone with the URL can too.

Point it at 192.168, 100.x or any URL.

ReqPad tests against LAN IPs, Tailscale addresses and tunnel URLs — six protocols, real auth, request history. Free to start.