Requex.me LogoRequex.me

Webhook FAQ

Real answers from someone who's debugged webhooks way too many times.

Q

What's a webhook tester and why do I need one?

A webhook tester gives you a public URL that captures incoming webhook requests in real-time. You point Stripe, GitHub, Shopify—whoever—at that URL, and you see exactly what they're sending you. Why? Because you can't always SSH into your production server to watch logs when testing a payment flow. A webhook tester sits in between and shows you the raw request: headers, signature, body, everything. Best part: no setup. Generate a URL, test your webhook handler without deploying anything, then move to production when you're confident.
Q

Is Requex completely free?

Yes. Unlimited webhooks, custom response codes, everything free. No account needed. webhook.site charges $9/month if you want to return a 500 status code or delay responses. Requex includes that for free. That's the main difference.
Q

How do webhooks differ from APIs?

APIs are pull. You ask "Do you have new data?" every 30 seconds. Waste of bandwidth if nothing changed. Webhooks are push. The service tells you instantly when something happens. Payment processed? You get notified right now, not in 30 seconds. That's it. Webhooks win for real-time updates.
Q

I'm getting a 401 Unauthorized on my webhook. What now?

Three things to check: 1. **Missing auth header** — Is your endpoint checking for Authorization, Bearer token, API key, etc.? Is it actually there in the request? 2. **Wrong secret** — If you're doing signature verification (HMAC), your webhook secret in the code must match what the provider has. Stripe rotates keys sometimes. 3. **Raw body not captured** — This trips up everyone. If you parse JSON first then do HMAC, it won't match. You need the raw bytes. Quick fix: capture your request in Requex, check if the Authorization header exists, and look at its value. That'll tell you what's actually being sent.
Q

How do I verify HMAC signatures from Stripe?

The provider sends a signature header (e.g., X-Stripe-Signature). You compute the same HMAC with your webhook secret and compare. const crypto = require('crypto'); app.post('/webhook', express.raw({ type: 'application/json' }), // Critical: raw body first (req, res) => { const sig = req.headers['x-stripe-signature']; const secret = process.env.STRIPE_WEBHOOK_SECRET; const hash = crypto .createHmac('sha256', secret) .update(req.body) // Raw bytes .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(hash))) { return res.status(401).send('Nope'); } const event = JSON.parse(req.body); console.log('Verified:', event.type); res.status(200).send('ok'); } ); The tricky part: use raw body bytes, not parsed JSON. And use timingSafeEqual so nobody can guess your secret through timing attacks.
Q

Can I simulate 500 errors and timeouts?

Yep. In your Requex dashboard, hit Response Settings and pick: - Status code (200, 500, anything) - Delay (up to 30 seconds) - Custom headers or body Why? You want to verify your client actually retries when it gets a 500. Or that it doesn't hang forever waiting for a response.
Q

How do I test Stripe webhooks locally?

Three ways: **Easiest (Requex):** Make a webhook here, add it to Stripe Dashboard → Developers → Webhooks. Test card is 4242 4242 4242 4242. Done. **Best (Stripe CLI):** Download stripe-cli, run: stripe listen --forward-to localhost:3000/webhook stripe trigger payment_intent.succeeded This forwards real Stripe events to your local port. You get the HMAC and everything. **Manual:** Capture payload in Requex, copy it, use Postman or curl to replay it locally. Slower but teaches you what's actually being sent.
Q

How do I prevent duplicate webhook processing?

Webhooks retry. If your handler crashes or times out, the provider sends it again. You need to handle that. Every webhook has an ID (Stripe calls it event.id). Store it in your database: app.post('/webhook', async (req, res) => { const eventId = req.body.id; // Already processed? const existing = await db.webhookEvents.findOne({ eventId }); if (existing) { return res.status(200).send('Already got this'); } // Process it await processPayment(req.body); // Mark as done await db.webhookEvents.create({ eventId }); res.status(200).send('ok'); }); Simple: one record per eventId. If it exists, skip. If not, process and create record. Now duplicate webhooks are harmless.
Q

I keep hitting rate limits on Stripe API calls from my webhook. Why?

Your webhook handler is calling Stripe API to do something (fetch user, create invoice, whatever). Stripe limits you to ~100 requests/second. If you process 200 webhooks concurrently, you'll hit that. Solution: queue your work instead of processing inline. const queue = require('bull'); const webhookQueue = new queue('webhooks'); app.post('/webhook', async (req, res) => { // Queue immediately, return 200 await webhookQueue.add(req.body); res.status(200).send('ok'); }); // Process at your own pace webhookQueue.process(async (job) => { // This will retry if it fails await processPaymentAndCallStripe(job.data); }); This way you return 200 instantly (so the provider doesn't retry), then process at whatever pace your rate limits allow. Job queue handles retries automatically.
Q

What's the difference between Requex and webhook.site?

Both free, both work. Requex has: - Custom responses free (webhook.site charges $9/mo) - WebSocket updates (webhook.site polls) - Dark mode webhook.site has: - Longer history (been around longer) - Slightly more documentation Honestly? Use Requex. Same job, better features free.
Q

How do I test GitHub webhooks?

GitHub dashboard → Settings → Webhooks → Add. Pick events (push, pull_request, issues) and paste your Requex URL. Then either push a commit or manually trigger in the Recent Deliveries section. You'll see the exact payload GitHub sent. The payload includes commit details, branch, author, timestamp. Useful for triggering CI/CD or updating project management tools.
Q

How do I test Shopify webhooks?

Shopify Admin → Settings → Apps and integrations → Webhooks → Create. Pick event (orders/created, products/updated, etc.) and paste your URL. You can send a test notification to see sample data. Common events: - orders/created → sync to accounting - products/updated → sync to marketplace - inventory_levels/update → update WMS Sign them with HMAC-SHA256, same as Stripe.
Q

What are the common webhook errors?

**400 Bad Request** — You sent malformed JSON or missing expected field. **401 Unauthorized** — Missing or wrong auth (bearer token, API key, HMAC signature). **404 Not Found** — Endpoint URL doesn't exist or got deleted. Provider stops sending after 8 tries. **429 Too Many Requests** — You're calling provider API too fast. Implement backoff. **500 Internal Server Error** — You crashed. Provider will retry this, which is good. **503 Service Unavailable** — Your server is down. Provider will retry. Tip: if you're not sure what's failing, capture it in Requex. Raw request, all headers. Way easier to debug.
Q

Do I need to respond immediately or can I process async?

Respond 200 immediately. Then queue your work. The provider waits about 5 seconds for a 200 response. If you don't respond fast, the provider thinks you're dead and retries. If you're doing heavy processing (calling APIs, updating database), that takes time. Better pattern: app.post('/webhook', (req, res) => { webhookQueue.add(req.body); // Queue it res.status(200).send('ok'); // Return fast }); webhookQueue.process(async (job) => { // Do expensive work here, no time pressure await heavyProcessing(job.data); }); Return 200 instantly. Process in background. This prevents timeouts and retries.
Q

How do I test Discord webhooks?

Discord webhooks are different from typical API webhooks. You're not receiving callbacks—you're sending messages. Server → Settings → Webhooks → New Webhook. Copy the URL. Send a POST request: curl -X POST https://discord.com/api/webhooks/123/abc -H "Content-Type: application/json" -d '{ "content": "Order #12345 complete", "embeds": [{ "title": "Payment Received", "description": "$99.99" }] }' Good for alerts, notifications, order updates. Not for receiving webhooks from Discord (they'd send via a different integration).
Q

How do I handle CORS errors in webhooks?

CORS errors happen in the browser, not on webhooks. Webhooks are server-to-server, no CORS involved. If you're getting a CORS error with a webhook tool, it's probably the testing tool itself having issues, not your handler. If your webhook handler makes requests to a different domain, that's not CORS—it's a normal API call. The server doesn't care about origins. What you might actually be hitting: missing auth headers, wrong endpoint URL, or firewall blocking.
Q

How do I forward incoming webhooks to multiple destinations?

Sometimes you want to send the same webhook to multiple places (your app AND a third-party service). Simple way: app.post('/webhook', async (req, res) => { // Send to multiple places at once await Promise.all([ fetch('https://slack.com/api/chat.postMessage', { /* ... */ }), fetch('https://analytics.example.com/events', { /* ... */ }), fetch('https://backup.example.com/webhooks', { /* ... */ }) ]); res.status(200).send('ok'); }); Better way: queue it. If one destination fails, don't retry them all: app.post('/webhook', async (req, res) => { // Queue three separate jobs await Promise.all([ forwardQueue.add({ dest: 'slack', data: req.body }), forwardQueue.add({ dest: 'analytics', data: req.body }), forwardQueue.add({ dest: 'backup', data: req.body }) ]); res.status(200).send('ok'); }); // Each destination retries independently forwardQueue.process(async (job) => { const { dest, data } = job.data; await sendToDestination(dest, data); });
Q

How do I test n8n or Make webhooks?

n8n and Make let you build automations visually. Webhooks are part of that. To test webhooks n8n sends: 1. Create a Webhook node in your workflow 2. Copy the Requex URL into that node 3. Execute the workflow 4. See the payload in Requex To test webhooks received by n8n: 1. Create a Webhook trigger in n8n 2. Copy the generated n8n URL 3. Paste it into another service (Stripe, GitHub, etc.) 4. When events happen, n8n receives them This is useful for debugging data transformations without needing a real backend.
Q

How do I handle webhook timeouts?

Webhook provider waits ~5 seconds for a 200 response. If your handler takes longer, it times out and the provider retries. Solution: return 200 immediately, process async. But what if you legitimately need time? For instance, processing a large order. That's what queues are for: app.post('/webhook', async (req, res) => { // Queue it NOW await queue.add(req.body); // Return 200 immediately res.status(200).send('ok'); }); // Process takes 30 seconds, no timeout queue.process(async (job) => { await expensiveProcessing(job.data); }); The provider doesn't care how long you actually process—it just needs the 200 response fast.
Q

How do I test Slack webhooks?

Slack webhooks are like Discord—you send messages, not receive them. Workspace → Settings → Apps → Custom Integrations → Incoming Webhooks → Create. POST to the URL: { "text": "New order received", "blocks": [{ "type": "section", "text": { "type": "mrkdwn", "text": "*Order #12345*\nAmount: $99.99" } }] } Good for alerts and notifications. If you need Slack to send webhooks to you, use their Events API instead (different setup).
Q

What's the max payload size for webhooks?

Depends on the provider. Most are generous: - Stripe: ~1MB - GitHub: ~25MB - Shopify: ~1MB But just because you can send 1MB doesn't mean you should. Larger payloads = slower processing. If a provider is sending huge payloads, consider: 1. Ask them to send minimal data + fetch details via API 2. Stream the request if possible 3. Set up gzip compression In practice, most webhooks are <100KB. If you're seeing >1MB, something's probably wrong.
Q

How do I test webhook retries?

Want to see if your handler actually handles retries? Use Requex to return an error: 1. Trigger a webhook 2. In Requex dashboard, set Response to 500 3. Provider sees the error and queues a retry 4. Few seconds later, another request arrives 5. Change Response back to 200 6. Second request succeeds This proves your provider implements retries. Good to know before production. For testing your own retry logic, use a job queue with exponential backoff. If processing fails, the queue retries automatically after 2s, 4s, 8s, etc.
Q

Can I use webhooks for real-time updates in a frontend?

Sort of, but it's complicated. Webhooks are server-to-server. Your frontend can't directly receive them without exposing an endpoint. Better approach: webhook hits your backend, backend pushes to frontend via WebSocket. // Backend receives webhook app.post('/webhook', async (req, res) => { const event = req.body; // Broadcast to connected clients via WebSocket io.emit('order_update', { orderId: event.data.id, status: 'paid' }); res.status(200).send('ok'); }); // Frontend listens socket.on('order_update', (data) => { console.log('Order paid:', data.orderId); updateUIImmediately(); }); This way your frontend gets instant updates when webhooks arrive.
Q

What's the difference between webhooks and pub/sub?

Webhooks: Service A sends HTTP POST to Service B. Direct, synchronous (mostly). Pub/Sub: Service A publishes to a message queue (Redis, Kafka, RabbitMQ). Service B subscribes. Decoupled. Webhooks: Simple, no infrastructure. Use for: alerts, notifications, triggered actions. Pub/Sub: More complex, but flexible. Use for: real-time systems, multiple consumers, guaranteed delivery. Webhooks are easier to start. If you outgrow them (need guaranteed delivery, multiple consumers, ordering), switch to pub/sub.
Q

How do I validate webhook timestamps to prevent replay attacks?

Some providers send a timestamp header. Check it to prevent old webhooks from being replayed. Example with Stripe: const signature = req.headers['x-stripe-signature']; const [timestamp, sig] = signature.split(',').map(p => p.split('=')[1]); // Reject if older than 5 minutes const now = Math.floor(Date.now() / 1000); if (now - parseInt(timestamp) > 300) { return res.status(401).send('Timestamp too old'); } // Then verify signature normally const hash = crypto .createHmac('sha256', secret) .update(`${timestamp}.${req.body}`) .digest('hex'); if (sig !== hash) { return res.status(401).send('Invalid signature'); } This prevents someone from capturing an old webhook and replaying it.
Q

How do I monitor webhook health?

Log every webhook: app.post('/webhook', async (req, res) => { const startTime = Date.now(); try { await processWebhook(req.body); logger.info('webhook_success', { type: req.body.type, duration: Date.now() - startTime }); res.status(200).send('ok'); } catch (err) { logger.error('webhook_failed', { type: req.body.type, error: err.message, duration: Date.now() - startTime }); res.status(500).send('error'); } }); Then set up alerts for: - More than 5 consecutive failures - Processing time > 5 seconds - Missing signature headers (possible attackers) Simple monitoring catches problems before they cascade.
Q

What's the difference between sync and async webhooks?

Sync: You process the webhook while the provider waits. Good for quick operations (<1s). Async: You queue it and return 200 immediately. Provider doesn't wait. Almost always use async. Here's why: if processing takes >5 seconds, the provider times out and retries. Now you have duplicates. So just queue it and return fast. The only time sync makes sense: simple validation. "Is this user allowed?" Nope, even then, queue it.
Q

How do I handle webhook encoding issues?

Webhooks usually come as UTF-8 JSON. But sometimes providers send weird encodings or special characters. Handle it: app.post('/webhook', express.json({ charset: 'utf-8' }), (req, res) => { // Normalize strings const text = req.body.text || ''; const normalized = text.normalize('NFD'); // Decompose accents // Or just ignore unprintable characters const clean = text.replace(/[^ -~]/g, ''); res.status(200).send('ok'); }); Most of the time this isn't an issue. But if you're processing user input from webhooks, normalize it.
Q

Can I use webhooks for notifications without a database?

Sure, but it's weird. Webhook hits your endpoint, you send a notification (email, SMS, Slack) immediately. app.post('/webhook', async (req, res) => { const order = req.body; // Send email immediately await mailer.send({ to: 'customer@example.com', subject: 'Order shipped', body: `Your order ${order.id} has shipped` }); res.status(200).send('ok'); }); Works fine for simple cases. But if email sending fails, the webhook fails. Better approach: queue it. And if you need to reference that webhook later ("Did we send a shipping notification?"), you need a database anyway.
Q

How do I load test my webhook endpoint?

Use a tool like k6: npm install -g k6 // load-test.js import http from 'k6/http'; export const options = { stages: [ { duration: '10s', target: 10 }, // Ramp to 10 users { duration: '30s', target: 50 }, // Ramp to 50 users { duration: '10s', target: 0 }, // Ramp down ], }; export default function () { http.post('https://requex.me/webhook-id', JSON.stringify({ type: 'payment.succeeded', data: { amount: 99.99 } })); } // Run it k6 run load-test.js Watch for: response times, error rate, queue backlog. If it all stays healthy under 50 concurrent requests, you're probably fine.
Q

What's the best way to debug webhook issues?

1. Capture in Requex — See the actual request, headers, body, signature 2. Log everything — Request received, processing start, processing end, errors 3. Isolate — Is it a parsing issue? Auth issue? Processing issue? 4. Replay — Capture the request, manually replay it with curl or Postman 5. Minimize — Test with the smallest possible payload first Most webhook problems are: - Wrong secret in environment variable - Raw body not captured (signature verification fails) - Timeout because processing takes too long - Some field is missing or renamed Use Requex to see the actual data, then debug locally.
Q

Can I send webhooks from my own app?

Yes. Just POST JSON to a URL. But don't reinvent the wheel. Use a webhook service like: - Svix — production-grade webhooks - Hookdeck — queuing + retries - Inngest — event triggering Or DIY with a queue: // User does something app.post('/api/order', async (req, res) => { const order = await db.orders.create(req.body); // Queue webhook await webhookQueue.add({ event: 'order.created', data: order }); res.json({ orderId: order.id }); }); // Send webhooks to subscribers webhookQueue.process(async (job) => { const subscribers = await db.webhookSubscriptions.findAll(); for (const sub of subscribers) { try { await fetch(sub.url, { method: 'POST', body: JSON.stringify(job.data), headers: { 'X-Webhook-Signature': signRequest(job.data, sub.secret) } }); } catch (err) { // Retry logic throw err; } } }); Hard part: retry logic, signature verification, subscriber management. That's why services exist.

Didn't find your answer?

Ask us directly. We're developers too, we get it.

Email us