Webhooks let external services notify your app when events happen — Stripe payments, GitHub pushes, Slack messages. This guide covers building a webhook endpoint, verifying signatures, and processing events reliably.
In this guide
A webhook is an HTTP POST request that an external service sends to your server when something happens. Instead of polling an API every minute to check for new events, the service calls you the moment the event occurs. Examples: Stripe calls your webhook when a payment succeeds, GitHub calls it when someone pushes code, Twilio calls it when an SMS is received. You register your webhook URL with the service and they handle the calling.
app.post("/webhook/stripe", express.raw({ type: "application/json" }), async (req, res) => { /* process event */ res.json({ received: true }); }). Important: use express.raw() (not express.json()) for Stripe — signature verification requires the raw body bytes. Respond with 200 quickly (within 5-30 seconds depending on the service). If processing takes longer, acknowledge immediately and process asynchronously in a background job.
Never process a webhook without verifying its signature — anyone who knows your URL can send fake events. For Stripe: const event = stripe.webhooks.constructEvent(req.body, req.headers["stripe-signature"], process.env.STRIPE_WEBHOOK_SECRET). If the signature is invalid, constructEvent throws — catch it and return 400. For GitHub: compare HMAC-SHA256 of the raw body using your webhook secret. Every major service documents their signature verification method.
Webhook services retry delivery if your endpoint returns a non-2xx response or times out. This means your endpoint may receive the same event multiple times. Make your handler idempotent — processing the same event twice should have the same effect as processing it once. Store processed event IDs in your database and skip events you have already handled: if (await db.eventProcessed(event.id)) return res.json({ received: true }).
Your localhost is not accessible from the internet, so services cannot call it during development. Use the Stripe CLI: stripe listen --forward-to localhost:3000/webhook/stripe. Or use ngrok: ngrok http 3000 gives you a public URL that tunnels to your localhost. Copy the ngrok URL into the service's webhook settings. Both tools display each webhook request and response in the terminal, making debugging easy.
Need Help?
Our engineering team handles implementations like this every week. Get a free scoping call — we will tell you exactly what it takes and what it costs.
Book a free callCompetitive Intelligence
Efficiency Modeling
© 2026 NexWorldTech — Built for Global Dominance.