DEV Community

Cover image for I finally stopped using ngrok for Stripe Webhooks.
Adrian Orenes
Adrian Orenes

Posted on

I finally stopped using ngrok for Stripe Webhooks.

Debugging Stripe Webhooks without the Alt-Tab Fatigue

I’ve spent an embarrassing amount of time in my career doing the "Stripe Dance."

You know the one: Trigger a test event in the Stripe dashboard, alt-tab to the terminal to see if ngrok caught it, alt-tab to the editor to tweak a console.log, then realize the ngrok tunnel timed out because you were over the free limit.

The Stripe CLI helps a lot, but you’re still squinting at a terminal buffer trying to find that one customer_id nested five levels deep in a JSON blob.

I recently started using the Hooklistener MCP server with Claude Code, and it’s the first time webhook integration hasn't felt like a chore. Here’s how the workflow actually feels.

The Setup (The boring part)

If you're using Claude Code, Cursor, or Windsurf, you just drop this into your MCP config. No binaries to install, no daemon to manage.

{
  "mcpServers": {
    "hooklistener": {
      "type": "streamable-http",
      "url": "https://app.hooklistener.com/api/mcp",
      "headers": { "Authorization": "Bearer hklst_your_key" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The Workflow: Let the AI do the "Watching"

The big shift here isn't just "seeing" the webhook; it's having the AI watch for it while you keep coding.

1. Creating a throwaway endpoint

Instead of a permanent staging URL, I just ask Claude: "Give me a new debug endpoint for Stripe Checkout." It hits the create_endpoint tool and gives me a URL. I paste that into Stripe once. Unlike ngrok, this URL stays alive.

2. The "Wait for it" moment

This is where it clicked for me. I was working on an Elixir handler for checkout.session.completed. I told Claude:

"Wait for a request on the Stripe endpoint. I'm going to trigger a test event now."

I went to Stripe, hit "Send test webhook," and instead of me hunting for the log, the payload just appeared in my chat.

3. Writing the code with context

Because the payload was right there in the conversation, I didn't have to copy-paste JSON into the prompt. I just said: "Based on that payload, write the handler. Make sure it pulls the 'plan_id' from the metadata."

The AI isn't guessing based on outdated docs; it's looking at the exact byte-for-byte request Stripe just sent.

Where this actually saves you

I found two specific scenarios where this beats the old terminal-tailing method:

  • Comparing Events: I captured a successful payment and a failed one. I asked Claude: "Compare these two payloads. Why did the metadata missing in the second one?" It did a diff and realized I’d forgotten to pass the client_reference_id in my frontend code. Doing that manually with two terminal windows is a headache.
  • Simulating the "What if": Hooklistener lets you set up mock responses. I set the endpoint to return a 500 to see how my app handled Stripe's retry logic. I could see the retry headers (Stripe-Should-Retry) appearing in the list_requests tool without leaving the editor.

Is it perfect?

No. You still have to go to the Stripe dashboard to paste the URL (Stripe doesn't have a "Create Webhook" API that works easily for local dev). And sometimes the AI gets over-excited and tries to explain the whole JSON structure when I just want one field.

But compared to the "Alt-Tab Dance"? I'm never going back. If you’re already using an AI assistant, adding the Hooklistener MCP is a 2-minute upgrade that pays for itself the next time you have to debug a nested JSON object.

Check it out at hooklistener.com.

Top comments (0)