
Debugging Webhooks Locally with APXY
Webhooks are notoriously hard to debug locally. APXY lets you intercept incoming webhook payloads, inspect them in real time, replay them without retriggering the source, and mock the endpoint for automated testing.
Webhooks are one of the trickiest APIs to debug locally. The source—Stripe, GitHub, Slack, or any third-party service—pushes events to your server. That means:
- You need a public URL (or a tunnel like ngrok) to receive them
- Retrigger requires the external event to happen again
- The payload only exists in memory until you log it
APXY simplifies this considerably. This tutorial covers how to capture incoming webhook payloads, inspect them, replay them without re-triggering the event, and write mock rules so your tests never need the real webhook at all.
Why webhooks are hard to debug
The standard debugging loop for webhooks looks like this:
- Trigger the event (complete a Stripe payment, push to GitHub, etc.)
- Hope the payload arrives
- Log the request body to a file or console
- Read the log and guess what went wrong
- Trigger the event again
This is slow. With APXY you collapse steps 1–5 into: look at the captured request.
Step 1: Install and start APXY
curl -fsSL https://apxy.dev/install.sh | bash
apxy start --port 8080Step 2: Expose your local server
Webhooks from external services need a public URL. Use any tunnel service—ngrok is the most common:
ngrok http 3000You will get a URL like https://abc123.ngrok.io. Use this as your webhook endpoint in the Stripe or GitHub dashboard.
Now route your local server through APXY so all incoming requests are captured. If your webhook server listens on port 3000, start it with the proxy:
HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080 node server.jsTip
For incoming webhooks specifically, you do not need the proxy to intercept the inbound POST—APXY captures the forwarded request as it reaches your server. The key is that your server processes requests while APXY watches.
Step 3: Trigger and capture
Send a test event from the webhook source. Stripe's dashboard has a "Send test event" button. GitHub's webhook settings have "Recent Deliveries" with a redeliver option.
Open APXY's Web UI at http://localhost:8081. You will see the incoming POST request in the traffic log with:
- Full URL and method
- All headers (including signature headers like
Stripe-SignatureorX-Hub-Signature-256) - Complete request body (JSON payload)
- Your server's response
Click the request to expand the detail view and inspect the payload fields.
Step 4: Inspect the signature header
Webhook signature verification is the most common source of bugs. The service sends a signature header; your server recalculates it from the raw body and a secret; if they do not match, you reject the event.
APXY shows you the exact raw body bytes that went over the wire. If your server is rejecting signatures, compare:
- The
Stripe-Signatureheader value from APXY - The secret you configured in your app
- Whether your server is reading
req.bodyas a parsed object (loses the raw bytes) vs a raw Buffer (correct)
This is nearly impossible to debug without seeing the actual request. APXY makes it the first thing you see.
Step 5: Replay without re-triggering
APXY's Replay feature re-sends the exact captured request—same headers, same body, same signature timestamp. No need to trigger another Stripe payment or GitHub push.
Select the captured webhook request in the UI, click Replay, and your server receives it again. This is especially useful when:
- The event is expensive to retrigger (a real payment, a production deploy)
- You need to test your handler logic with the same payload multiple times
- You want to confirm your fix actually worked
Note
Some webhook providers include a timestamp in the signature (Stripe does). If you replay a webhook too long after capture, the signature may fail validation. APXY lets you edit request headers before replaying, so you can update the timestamp and recalculate the signature if needed.
Step 6: Mock the webhook for automated tests
For CI and unit tests, you do not want to depend on real webhook delivery. Create a mock rule that simulates the incoming webhook so your tests work offline:
apxy rules add \
--match "POST /webhooks/stripe" \
--status 200 \
--body '{"received":true}' \
--header "Content-Type: application/json"Then send a test payload from your test suite directly through APXY:
curl -x http://localhost:8080 \
-X POST http://localhost:3000/webhooks/stripe \
-H "Content-Type: application/json" \
-H "Stripe-Signature: t=1234,v1=fakesig" \
-d '{"type":"payment_intent.succeeded","data":{"object":{"id":"pi_test"}}}'Your test hits your actual webhook handler, your handler processes the payload, and APXY records what happened. No Stripe account needed.
Common webhook debugging patterns
| Issue | What to check in APXY |
|---|---|
| 400 Bad Request | Request body shape -- compare to the API's event schema |
| 401 Unauthorized | Signature header value and raw body bytes |
| 500 Server Error | Your server's response body for the error message |
| Event not processed | Whether the type field in the payload matches your handler's switch |
| Duplicate events | Whether the id field is the same across replays |
What to do next
Once you are comfortable capturing and replaying webhooks, the natural extension is the share-pack workflow: export the captured webhook as a reproducible artifact and share it with your team. See the Stripe checkout 422 share pack for an example of this pattern applied to a real debugging scenario.
Debug your APIs with APXY
Capture, inspect, mock, and replay HTTP/HTTPS traffic. Free to install.
Install FreeRelated articles
Dynamic API Responses: Scripting with JavaScript in APXY
Static mock rules return the same response every time. When you need to inject dynamic data, modify responses conditionally, or simulate real API behavior like rate limiting, APXY's JavaScript scripting engine handles it.
TutorialRecord and Replay API Traffic for Regression Testing with APXY
Record real API traffic once, convert it to mock rules, and replay those exact responses against future code changes. Catch regressions before they reach production without maintaining a live test environment.