Webhooks Site: Test, Inspect, and Ship Payment Webhooks
You hit "create webhook," paste an endpoint URL, click save, and then nothing happens. Or worse, your signature check fails and you spend two hours wondering if it's your secret, your raw body parser, or your tunnel dropping the request.
Every indie builder who's wired up a Stripe, Paddle, or LemonSqueezy webhook has lived this. The fix is a webhooks site: a public URL that catches whatever your payment provider throws at it so you can actually see the payload, the headers, and the signature before you write a single line of handler code.
This is the field guide. What "webhook.site" really means, how to use it to debug payment webhooks end-to-end, and what your production handler needs to look like once the inspection is done.
What is a webhooks site
A webhooks site is a free public URL that captures HTTP requests sent to it and shows them in a browser. You paste the URL into a sender (Stripe, GitHub, a contact form, your payment processor), trigger an event, and the request lands in a list you can click through. You see the body, the headers, the query params, and the source IP.
The canonical one is webhook.site. Open it, get a unique URL like https://webhook.site/8f3a..., and every POST that hits it is captured and displayed. No signup. No code. No deploy.
Other options: requestbin.com, pipedream.com (more powerful, you can write JS to transform the request), ngrok (tunnels to localhost, you actually run code).
Most builders default to webhook.site for the first 80% of webhook work. It's free, it's instant, and it's how you find out what your payment provider is actually sending you.
Why payment webhooks break
Payment webhooks are not regular webhooks. Three things eat developer time and they're the same three every time.
Raw body matters. Signature verification reads the raw bytes of the request, not the parsed JSON. If your framework auto-parses the body (Express with app.use(express.json()), Next.js API routes by default, FastAPI's await request.json()), the bytes you sign-check are not the bytes the provider signed. The hash fails. You think your secret is wrong. It is not. You need a raw body buffer at the route level.
Retry semantics. Payment processors retry on any non-2xx response. If your handler does work before responding 200, and the work is slow, you'll get duplicate events. Idempotency keys live in the event payload, store them, dedupe on insert.
Event ordering is not guaranteed. subscription.updated can land before subscription.created. Your handler has to be order-independent or you build a small state machine that reconciles.
A webhooks site lets you see all of this before you touch your handler. Send a test event, look at what's actually in the body and headers, then write the handler against the real shape.
How to test a payment webhook in 5 minutes
The flow is the same regardless of your processor. Stripe, Paddle, LemonSqueezy, Creem, all the same shape.
- Go to webhook.site and copy your unique URL.
- In your payment dashboard, create a new webhook endpoint, paste the URL, select the events you care about (
payment.succeeded,subscription.created, etc.). - Trigger a test event from the dashboard. Most processors have a "send test webhook" button.
- Reload webhook.site. The request shows up in the left pane. Click it.
- Inspect the body, the headers (look for the signature header, usually
X-SignatureorStripe-Signatureorcreem-signature), and copy the raw payload.
You now know exactly what your handler is going to receive. You can copy the payload into a test fixture, write the handler against it, and ship.
What a production handler looks like
Once you've inspected on a webhooks site and you know the payload shape, the handler is mechanical. Three rules.
Read the raw body. In Next.js App Router:
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get("creem-signature");
if (!verify(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
// dispatch on event.type
return new Response("ok");
}
In Express, mount express.raw({ type: 'application/json' }) on the webhook route only. Don't apply express.json() globally if you have webhook routes.
Respond 200 fast. Acknowledge first, work later. Push the event into a queue or a background job, return 200 within a couple of seconds. If your handler does database writes, external API calls, and email sends inline, you'll hit timeouts and get duplicate retries.
Idempotency on the event id. Every payment event has a unique id. Store it. If you see it again, no-op. This is the single line of code that prevents the "user got charged once but got the welcome email three times" support ticket.
Why builders pick creem for payments + webhooks
If you're picking a payment processor and webhooks are part of the equation, the thing that matters is how cleanly the events are documented and how fast you can get from "create endpoint" to "production handler shipping."
Creem ships webhook notifications for every payment lifecycle event (payment.succeeded, subscription.created, subscription.updated, subscription.cancelled, refund.created, and the full set). The events fire from api.creem.io in production and test-api.creem.io in sandbox, so you can wire your handler against test mode, point it at webhook.site, and confirm the shape before you flip to live.
The economics line up too: 3.9% + 40¢ flat, no monthly fees, no setup costs (creem.io/pricing). You're not paying a platform fee for the webhook infrastructure. It's bundled with the merchant of record service that handles VAT, GST, and sales tax in 190+ countries.
Full webhook event docs live at docs.creem.io. The TypeScript SDK and Next.js adapter both ship signature verification helpers so you're not hand-rolling HMAC.
When you should not use a webhooks site
Webhook.site and friends are inspection tools. They are not endpoints. Three cases where you need something else.
Local development with real code. Use ngrok (or the Stripe CLI / creem CLI which proxy events to localhost). Inspection tools show you the payload, but if you want your handler running locally against a live event stream, you need a tunnel.
Long-running staging. Webhook.site URLs expire and the free tier rate-limits. If you want a persistent staging endpoint, deploy your handler to a preview environment (Vercel, Cloudflare Workers, Railway) and point the processor at it directly.
Anything with PII. Webhook.site is public. Anyone with the URL can see the requests. Don't paste it into a production webhook config and don't send real customer data through it. Use test mode events only.
What to do this week
If you're about to wire webhooks for the first time, the three-step path:
- Open webhook.site and grab a URL. Configure it as a test webhook in your payment dashboard. Trigger 2 or 3 events and study the payloads.
- Write your handler against the inspected payload. Raw body, signature check, idempotency, fast 200.
- Swap the webhook.site URL for your production endpoint and turn on the live event stream. Monitor for the first few hours.
If you're picking a payment processor and webhook DX matters, read the creem docs and try the test mode flow. The webhook shape is documented, the SDK ships verification helpers, and the events fire predictably from the moment you create the endpoint.
FAQ
What is webhook.site used for?
Webhook.site is a free public URL that captures HTTP POST requests and displays them in a browser. Developers use it to inspect the body, headers, and signature of webhooks sent by services like Stripe, GitHub, Paddle, and creem before writing a handler. It removes the guesswork from webhook integration.
Is webhook.site safe for payment webhooks?
Only for test mode. Webhook.site URLs are public. Anyone with the URL can read the captured requests. Never point a live payment webhook at webhook.site. Use it for sandbox or test mode events only, then swap to a real endpoint on your domain for production.
How do I test a webhook without writing code?
Use webhook.site or requestbin.com. Both give you a public URL that captures requests. Configure your sender (payment processor, GitHub, form tool) to send to that URL, trigger the event, and inspect the captured request in the browser. No code, no deploy.
Why does my webhook signature verification keep failing?
The most common cause is body parsing. Signature verification hashes the raw request bytes. If your framework parses the JSON before your handler runs (Express default, Next.js Pages Router default), the bytes you check do not match the bytes the provider signed. Mount a raw body parser on the webhook route only.
Does creem support webhooks?
Yes. Creem fires webhooks for every payment lifecycle event: payment.succeeded, subscription.created, subscription.updated, subscription.cancelled, refund.created, and more. Full event reference at docs.creem.io. Test mode endpoints fire from test-api.creem.io so you can wire and verify before going live.
What is the difference between webhook.site and ngrok?
Webhook.site captures requests and displays them in a browser. You see the payload, you do not run code. Ngrok tunnels a public URL to a port on your localhost, so requests actually hit your local handler. Use webhook.site to inspect, use ngrok to develop against live events.
