What Are Webhooks and How Do They Work

What Are Webhooks and How Do They Work

Table of Contents

Introduction: The Real-World Problem Webhooks Solve

Imagine you run an online store using Shopify. A customer just bought a $500 laptop. You need to:

  • Update your inventory system
  • Send a confirmation email
  • Notify your warehouse to pack the item
  • Update your accounting software
  • Alert your fulfillment partner to ship it

Without webhooks, you’d manually check your Shopify store every 5 minutes to see if new orders came in. Or you’d write code to keep asking Shopify “did anyone buy something?” every few seconds (polling). Both approaches waste time and resources.

Webhooks solve this elegantly: The moment the customer completes their purchase, Shopify automatically sends a notification to all your systems. Everyone gets updated instantly. No polling. No delays. Not wasted server resources.

Key Insight: Webhooks are the difference between your systems constantly asking for updates (inefficient) and receiving updates the moment something happens (efficient).

In this comprehensive guide, you’ll learn exactly what webhooks are, how they work, how to implement them in your favorite programming language, how to secure them, and how to troubleshoot when things go wrong.

What Are Webhooks? Simple Definition

The Simplest Explanation

webhook is an HTTP callback — basically an automated notification that one application sends to another when something interesting happens.

Definition: A webhook is an event-driven, real-time notification sent from one application to another via HTTP POST request when a specific event occurs.

Webhook vs Regular Function Callback: What’s the Difference?

In programming, a “callback” is a function you provide to be called later when something happens. A webhook is the same idea, but it works across the web via HTTP.

Example:

  • Regular Callback: “When the button is clicked, run this function”
  • Webhook: “When a payment is made, send an HTTP request to this URL”

The “Doorbell” Analogy That Actually Makes Sense

Think of webhooks like a smart doorbell that notifies you the moment someone arrives, rather than you checking your door every 5 minutes.

  • Without webhooks (polling): You check your front door every 5 minutes: “Is anyone here? No. Is anyone here? No. Oh! Someone is here!” — By this time, they’ve been waiting.
  • With webhooks: The doorbell rings instantly when someone arrives. You get notified immediately.

Key Characteristics of Webhooks

CharacteristicDescription
Event-DrivenTriggered by something happening (payment made, order placed, user signed up)
Real-TimeNotification sent instantly, not delayed or batched
One-Way CommunicationServer sends data to your endpoint (push model, not pull)
LightweightUses HTTP POST requests, no complex setup needed
AutomatedOnce set up, they fire automatically without manual intervention
Uses HTTP/HTTPSWorks over standard web protocols, no special infrastructure needed

The Origin of the Term “Webhook”

The term “webhook” combines two concepts:

  • Web: Referring to HTTP-based communication over the internet
  • Hook: A programming concept where you “hook” into events to run custom code

The term was popularized by developer Jeff Lindsay in his influential 2007 blog post titled “Web hooks to revolutionize the web,” which introduced the concept to the broader developer community.

How Do Webhooks Work? Step-by-Step

The 5-Step Webhook Process

Visual: 5-Step Webhook Flow Diagram
(Setup → Event Occurs → HTTP Request → Processing → Response)

Step 1: Configuration & Setup

First, you register your webhook with the source application. You tell them:

  • Webhook URL: Where to send the notification (your endpoint)
  • Events to Subscribe To: Which events trigger the webhook (e.g., “new order”, “payment received”)
  • Authentication: How to authenticate the request (secret key, API token, etc.)

Example: You set up a Shopify webhook that says: “When a new order is created, send a POST request to https://myapp.com/webhooks/orders with order details and sign it with secret key ‘abc123′”

Step 2: Event Occurs in Source System

Something happens in the source application that matches your subscription. Examples:

  • A customer completes a purchase (Shopify)
  • A payment is processed (Stripe)
  • Code is pushed to a repository (GitHub)
  • A form is submitted (Typeform)
  • A meeting is scheduled (Calendly)

Step 3: HTTP POST Request is Sent

The source application immediately sends an HTTP POST request to your webhook URL with data about the event. The request includes:

Anatomy of a Webhook Request

HTTP Headers: POST /webhooks/orders HTTP/1.1 Host: myapp.com Content-Type: application/json X-Webhook-Signature: sha256=abcd1234… User-Agent: Shopify/2.0 Request Body (JSON Payload): { “id”: 12345, “event”: “order.created”, “timestamp”: “2026-04-30T10:30:00Z”, “data”: { “order_id”: “ORD-98765”, “customer_email”: “john@example.com”, “amount”: 99.99, “items”: [ { “name”: “Webhook Guide eBook”, “quantity”: 1, “price”: 99.99 } ] } }

Step 4: Your Application Processes the Webhook

Your server receives the POST request at the webhook URL. Your application:

  1. Validates the request: Checks the signature to ensure it came from the trusted source
  2. Parses the JSON payload: Extracts the event data
  3. Performs actions: Updates database, sends email, triggers workflows, etc.
  4. Sends a response: Returns HTTP 200 OK to confirm receipt

Step 5: Webhook Provider Receives Acknowledgment

The source application checks your response:

  • HTTP 200 OK: “Great! You got it. Webhook delivery successful.”
  • HTTP 4xx/5xx: “Error received. Will retry later.”

⚠️ Important: If your webhook endpoint returns an error or doesn’t respond within the timeout (usually 5-30 seconds), the webhook provider will retry the request. Most services retry with exponential backoff (1 sec, 2 sec, 4 sec, 8 sec, etc.).

Complete Webhook Flow Diagram

Webhook Request-Response Timing

PhaseTimeWhat Happens
T=0msImmediateEvent occurs in source application
T=1-10msMillisecondsSource app creates webhook payload
T=10-100msMillisecondsHTTP request sent over internet
T=100-500msMilliseconds to secondsYour server processes webhook
T=500-1000msMilliseconds to secondsYou send HTTP 200 response
Total<1 secondFrom event to your system knowing about it

Real Example: Stripe Payment Webhook

Here’s what actually happens when you receive a payment on Stripe:

  1. Customer enters credit card on your payment form
  2. Your JavaScript code sends the card details to Stripe (PCI compliant)
  3. Stripe processes the payment and creates a “charge.succeeded” event
  4. Stripe immediately sends an HTTP POST to your webhook endpoint: https://myapp.com/webhooks/stripe
  5. Your webhook handler receives the request with payment details
  6. Your code updates the customer’s subscription, sends confirmation email, updates your accounting software
  7. You return HTTP 200 OK
  8. Stripe logs the successful delivery
  9. All of this happens within 100-500ms

Webhooks vs APIs, Polling & WebSockets: Which Should You Use?

Webhooks aren’t the only way applications communicate. Let’s compare them to other methods.

Webhooks vs REST APIs

AspectWebhooksREST APIs
Who Initiates?Server (push model)Client (pull model)
Real-Time?Yes, instantDepends on polling frequency
Resource UsageLow (no constant requests)High (constant polling)
Setup ComplexitySimple (just provide URL)Complex (full API implementation)
Operations SupportedEvents only (read-only)CRUD (Create, Read, Update, Delete)
Use CaseWhen you need instant notificationsWhen you need full data access

Webhook Example with REST API

Scenario: Track new Shopify orders

Using Webhooks (Efficient):

  • Set up webhook endpoint
  • Shopify sends notification when order placed
  • Your app processes immediately
  • Zero bandwidth wasted on checks

Using REST API Polling (Inefficient):

  • Every 30 seconds, your code calls Shopify API: “Any new orders?”
  • Shopify responds with latest 100 orders
  • You filter for new ones
  • If no new orders, you still made an API call (wasted bandwidth)
  • If an order comes in, there’s a 0-30 second delay before you know

Webhooks vs Polling: Resource Comparison

MetricWebhooksPolling Every 30sPolling Every 5s
API Calls/HourOnly when event occurs120720
Network RequestsMinimalVery highExtremely high
Server CPULow (event-driven)Moderate (constant polling)High (very frequent polling)
Latency<100ms0-30s delay0-5s delay
CostLowHigher (more API calls)Much higher

Webhooks vs WebSockets

WebSockets create a persistent, two-way connection between client and server. They’re different from webhooks:

AspectWebhooksWebSockets
Connection TypeHTTP (one-way, temporary)TCP (two-way, persistent)
Communication DirectionServer → Client onlyBidirectional (both ways)
LatencyLow (<100ms)Very low (<10ms)
Use CasesAsync events, notifications, integrationsReal-time chat, live updates, gaming
InfrastructureSimple HTTP serverSpecialized WebSocket server

💡 TL;DR: Webhooks and APIs solve different problems. Webhooks = “notify me when something happens”. APIs = “let me ask you for data anytime”. WebSockets = “let’s have a live conversation”.

15+ Real-World Webhook Use Cases

Webhooks are used everywhere. Here are practical examples from major companies:

1. E-Commerce & Payments

Stripe: Payment Processing

When a customer’s payment is processed through Stripe, you receive webhooks for:

  • charge.succeeded – Payment went through, activate subscription
  • charge.failed – Payment declined, send retry email
  • charge.refunded – Refund processed, update customer account
  • invoice.paid – Monthly subscription payment received

Shopify: Order Processing

Shopify sends webhooks for order lifecycle events:

  • orders/created – New order received → notify warehouse
  • orders/updated – Order modified → update fulfillment
  • fulfillments/created – Item shipped → send tracking email
  • inventory_levels/update – Stock changed → update website

2. Developer Tools & CI/CD

GitHub: Code Changes

  • push – Code pushed to repo → trigger tests, deploy
  • pull_request – PR opened → run CI checks, request reviews
  • issues – Issue opened → create Jira ticket
  • release – New release → trigger production deployment

GitLab & Jenkins: Continuous Integration

When webhooks detect code changes, automated pipelines:

  • Run unit tests
  • Build Docker containers
  • Deploy to staging environment
  • Run integration tests
  • Deploy to production (if tests pass)

3. Communication & Messaging

Twilio: SMS & Voice

Webhook events for messaging:

  • message.sent – SMS delivered
  • message.failed – SMS bounced, retry with email
  • call.completed – Phone call ended, save recording
  • call.incoming – Incoming call, route to agent

SendGrid: Email Delivery

  • delivered – Email reached inbox
  • opened – User opened email, track engagement
  • clicked – User clicked link, record conversion
  • bounced – Invalid email, remove from list
  • unsubscribe – User opted out, respect preference

4. CRM & Marketing Automation

HubSpot: Lead Management

  • Contact created → add to welcome email sequence
  • Deal stage changed → notify sales team
  • Form submitted → create CRM record
  • Email opened 3x → mark as “high intent”, trigger call

Mailchimp: Email Marketing

  • New subscriber → send welcome email
  • Unsubscribe → remove from all campaigns
  • Campaign sent → log open/click rates
  • Bounced email → move to separate list

5. Productivity & Project Management

Slack: Notifications

Webhooks trigger Slack messages for:

  • New customer signup → #sales channel
  • Production error → #devops channel with error details
  • Customer feedback received → #support channel
  • Deploy completed → #engineering channel

Asana & Trello: Task Management

  • GitHub push → create Asana task “Review PR #456”
  • Form submission → create Trello card in “To Do” list
  • Task due date approaching → send Slack reminder

6. Calendar & Scheduling

Calendly: Meeting Scheduling

  • Meeting booked → send calendar invite
  • Meeting rescheduled → update all attendees
  • Meeting cancelled → send cancellation notice
  • 1 hour before → send reminder email

Real-World Webhook Integration Example

Example Workflow: E-commerce Order to Fulfillment

  1. Customer orders on Shopify
  2. Shopify sends orders/created webhook to your app
  3. Your app receives webhook, validates signature
  4. Your app simultaneously:
    • Adds order to database
    • Sends order details to warehouse management system via webhook
    • Sends customer confirmation email via SendGrid webhook
    • Posts message in #orders Slack channel
    • Updates analytics database
  5. Warehouse system receives webhook, prints packing slip
  6. Customer receives email confirmation
  7. Team sees order in Slack
  8. All happens within 1-2 seconds of purchase

How to Implement Webhooks (6 Languages)

Let’s build a complete webhook receiver that works with Stripe webhooks. I’ll show examples in 6 popular languages.

Before You Start: Required Setup

  1. Choose your programming language
  2. Create a webhook endpoint URL (e.g., https://myapp.com/webhooks/stripe)
  3. Register webhook with service provider (Stripe, GitHub, etc.)
  4. Get your webhook signing secret from the provider
  5. Test locally with ngrok or RequestBin

Testing Webhooks Locally with ngrok

When developing locally, your webhook endpoint isn’t accessible from the internet. Use ngrok to expose it:

Terminal: npm install -g ngrok # Run your server on localhost:3000 npm start # In another terminal, expose it to internet ngrok http 3000 # You'll get a URL like: https://abc123.ngrok.io # Use this URL to register webhooks with services like Stripe

1. Node.js / Express Webhook Receiver

webhook.js - Complete Stripe webhook handler const express = require('express'); const crypto = require('crypto'); const app = express(); // Your Stripe webhook signing secret const WEBHOOK_SECRET = 'whsec_your_secret_key_here'; // Middleware to handle raw body (important for signature verification) app.use(express.raw({type: 'application/json'})); // Webhook endpoint app.post('/webhooks/stripe', async (req, res) => { const sig = req.headers['stripe-signature']; try { // 1. Verify the webhook signature const event = verifyWebhookSignature( req.body, sig, WEBHOOK_SECRET ); // 2. Handle different event types switch(event.type) { case 'charge.succeeded': await handlePaymentSuccess(event.data.object); break; case 'charge.failed': await handlePaymentFailed(event.data.object); break; case 'invoice.paid': await handleInvoicePaid(event.data.object); break; default: console.log(`Unhandled event type: ${event.type}`); } // 3. Return success response res.status(200).json({received: true}); } catch (error) { console.error('Webhook error:', error); // Return 400 to tell Stripe there was a problem res.status(400).send(`Webhook Error: ${error.message}`); } }); // Verify webhook signature using HMAC-SHA256 function verifyWebhookSignature(body, signature, secret) { const hash = crypto .createHmac('sha256', secret) .update(body) .digest('hex'); // Compare signatures safely if (!compareSecure(hash, signature)) { throw new Error('Webhook signature verification failed'); } return JSON.parse(body); } // Safe string comparison (prevents timing attacks) function compareSecure(a, b) { return crypto.timingsSafeEqual( Buffer.from(a), Buffer.from(b) ); } // Handle successful payment async function handlePaymentSuccess(charge) { console.log(`Payment succeeded: $${charge.amount/100}`); // Update database // Send confirmation email // Trigger fulfillment } // Handle failed payment async function handlePaymentFailed(charge) { console.log(`Payment failed: ${charge.failure_message}`); // Mark subscription as unpaid // Send retry email } // Handle invoice paid async function handleInvoicePaid(invoice) { console.log(`Invoice ${invoice.id} paid`); // Activate subscription // Send receipt } app.listen(3000, () => console.log('Webhook server running on port 3000'));

2. Python / Flask Webhook Receiver

webhook.py - Flask webhook handler from flask import Flask, request, jsonify import hmac import hashlib import json app = Flask(__name__) WEBHOOK_SECRET = 'whsec_your_secret_key_here' @app.route('/webhooks/stripe', methods=['POST']) def stripe_webhook(): sig = request.headers.get('stripe-signature') payload = request.get_data(as_text=True) try: # Verify webhook signature if not verify_webhook(payload, sig, WEBHOOK_SECRET): return jsonify({'error': 'Invalid signature'}), 400 event = json.loads(payload) # Handle different event types if event['type'] == 'charge.succeeded': handle_payment_success(event['data']['object']) elif event['type'] == 'charge.failed': handle_payment_failed(event['data']['object']) elif event['type'] == 'invoice.paid': handle_invoice_paid(event['data']['object']) return jsonify({'received': True}), 200 except Exception as e: print(f'Webhook error: {str(e)}') return jsonify({'error': str(e)}), 400 def verify_webhook(payload, signature, secret): """Verify webhook signature using HMAC-SHA256""" hash_object = hmac.new( secret.encode(), payload.encode(), hashlib.sha256 ) expected_sig = hash_object.hexdigest() return hmac.compare_digest(expected_sig, signature) def handle_payment_success(charge): print(f"Payment succeeded: ${charge['amount']/100}") # Update database, send email, etc. def handle_payment_failed(charge): print(f"Payment failed: {charge['failure_message']}") # Mark as unpaid, send retry email def handle_invoice_paid(invoice): print(f"Invoice {invoice['id']} paid") # Activate subscription if __name__ == '__main__': app.run(port=3000, debug=True)

3. PHP / Laravel Webhook Receiver

WebhookController.php - Laravel webhook handler namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; class WebhookController extends Controller { const WEBHOOK_SECRET = 'whsec_your_secret_key_here'; public function handleStripe(Request $request) { $payload = $request->getContent(); $sig = $request->header('stripe-signature'); try { // Verify webhook signature if (!$this->verifySignature($payload, $sig)) { return response()->json(['error' => 'Invalid signature'], 400); } $event = json_decode($payload, true); // Handle different event types switch ($event['type']) { case 'charge.succeeded': $this->handlePaymentSuccess($event['data']['object']); break; case 'charge.failed': $this->handlePaymentFailed($event['data']['object']); break; case 'invoice.paid': $this->handleInvoicePaid($event['data']['object']); break; } return response()->json(['received' => true]); } catch (\Exception $e) { Log::error('Webhook error: ' . $e->getMessage()); return response()->json(['error' => $e->getMessage()], 400); } } private function verifySignature($payload, $signature) { $computed = hash_hmac( 'sha256', $payload, self::WEBHOOK_SECRET ); return hash_equals($computed, $signature); } private function handlePaymentSuccess($charge) { Log::info("Payment succeeded: " . $charge['amount']); // Update database, send email, etc. } private function handlePaymentFailed($charge) { Log::info("Payment failed: " . $charge['failure_message']); // Mark as unpaid } private function handleInvoicePaid($invoice) { Log::info("Invoice paid: " . $invoice['id']); // Activate subscription } }

4. Ruby / Rails Webhook Receiver

webhooks_controller.rb - Rails webhook handler class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token WEBHOOK_SECRET = 'whsec_your_secret_key_here' def stripe payload = request.body.read sig = request.headers['Stripe-Signature'] begin # Verify webhook signature verify_signature(payload, sig) event = JSON.parse(payload) # Handle different event types case event['type'] when 'charge.succeeded' handle_payment_success(event['data']['object']) when 'charge.failed' handle_payment_failed(event['data']['object']) when 'invoice.paid' handle_invoice_paid(event['data']['object']) end render json: { received: true }, status: 200 rescue StandardError => e Rails.logger.error("Webhook error: #{e.message}") render json: { error: e.message }, status: 400 end end private def verify_signature(payload, signature) computed = OpenSSL::HMAC.hexdigest('sha256', WEBHOOK_SECRET, payload) raise 'Invalid signature' unless secure_compare(computed, signature) end def secure_compare(a, b) Rack::Utils.secure_compare(a, b) end def handle_payment_success(charge) Rails.logger.info("Payment succeeded: #{charge['amount']}") # Update database, send email, etc. end def handle_payment_failed(charge) Rails.logger.info("Payment failed: #{charge['failure_message']}") # Mark as unpaid end def handle_invoice_paid(invoice) Rails.logger.info("Invoice paid: #{invoice['id']}") # Activate subscription end end

5. Go / Gin Webhook Receiver

webhook.go - Gin webhook handler package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "github.com/gin-gonic/gin" ) const WEBHOOK_SECRET = "whsec_your_secret_key_here" func main() { r := gin.Default() r.POST("/webhooks/stripe", handleStripeWebhook) r.Run(":3000") } func handleStripeWebhook(c *gin.Context) { payload, _ := ioutil.ReadAll(c.Request.Body) sig := c.GetHeader("stripe-signature") // Verify signature if !verifySignature(payload, sig) { c.JSON(400, gin.H{"error": "Invalid signature"}) return } var event map[string]interface{} json.Unmarshal(payload, &event) // Handle different event types eventType := event["type"].(string) data := event["data"].(map[string]interface{}) object := data["object"].(map[string]interface{}) switch eventType { case "charge.succeeded": handlePaymentSuccess(object) case "charge.failed": handlePaymentFailed(object) case "invoice.paid": handleInvoicePaid(object) } c.JSON(200, gin.H{"received": true}) } func verifySignature(payload []byte, signature string) bool { h := hmac.New(sha256.New, []byte(WEBHOOK_SECRET)) h.Write(payload) expected := hex.EncodeToString(h.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } func handlePaymentSuccess(charge map[string]interface{}) { fmt.Printf("Payment succeeded: %v\n", charge["amount"]) } func handlePaymentFailed(charge map[string]interface{}) { fmt.Printf("Payment failed: %v\n", charge["failure_message"]) } func handleInvoicePaid(invoice map[string]interface{}) { fmt.Printf("Invoice paid: %v\n", invoice["id"]) }

6. Java / Spring Boot Webhook Receiver

WebhookController.java - Spring Boot webhook handler import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; @RestController @RequestMapping("/webhooks") public class WebhookController { private static final String WEBHOOK_SECRET = "whsec_your_secret_key_here"; private ObjectMapper objectMapper = new ObjectMapper(); @PostMapping("/stripe") public ResponseEntity handleStripeWebhook( @RequestBody String payload, @RequestHeader("stripe-signature") String signature ) { try { // Verify signature if (!verifySignature(payload, signature)) { return ResponseEntity.badRequest().body("Invalid signature"); } JsonNode event = objectMapper.readTree(payload); String eventType = event.get("type").asText(); JsonNode object = event.get("data").get("object"); // Handle different event types switch (eventType) { case "charge.succeeded": handlePaymentSuccess(object); break; case "charge.failed": handlePaymentFailed(object); break; case "invoice.paid": handleInvoicePaid(object); break; } return ResponseEntity.ok().body("{\"received\": true}"); } catch (Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } } private boolean verifySignature(String payload, String signature) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec keySpec = new SecretKeySpec( WEBHOOK_SECRET.getBytes(), "HmacSHA256" ); mac.init(keySpec); String computed = bytesToHex(mac.doFinal(payload.getBytes())); return computed.equals(signature); } private void handlePaymentSuccess(JsonNode charge) { System.out.println("Payment succeeded: " + charge.get("amount")); } private void handlePaymentFailed(JsonNode charge) { System.out.println("Payment failed: " + charge.get("failure_message")); } private void handleInvoicePaid(JsonNode invoice) { System.out.println("Invoice paid: " + invoice.get("id")); } private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } }

Setting Up Webhooks in Popular Platforms

Stripe Webhook Setup

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click “Add Endpoint”
  3. Enter your endpoint URL: https://myapp.com/webhooks/stripe
  4. Select events: charge.succeeded, charge.failed, invoice.paid
  5. Copy the “Signing Secret” (starts with whsec_)
  6. Add secret to your environment variables
  7. Test with Stripe CLI

GitHub Webhook Setup

  1. Go to Repository Settings → Webhooks
  2. Click “Add webhook”
  3. Payload URL: https://myapp.com/webhooks/github
  4. Content type: application/json
  5. Select events: push, pull_request, release
  6. Add secret key for verification
  7. Click “Add webhook”

Webhook Testing Best Practices

ToolPurposeBest For
ngrokExpose local server to internetLocal development
Webhook.siteReceive & inspect webhooksQuick testing
RequestBinCapture webhook requestsDebugging
PostmanManual webhook testingUnit testing
Stripe CLITest Stripe webhooks locallyStripe development

💡 Testing Workflow:

  1. Start your local server: npm start
  2. Expose with ngrok: ngrok http 3000
  3. Register webhook with ngrok URL in your provider’s dashboard
  4. Trigger test event in provider’s dashboard
  5. Check your server logs for webhook receipt

Webhook Security Best Practices

Webhooks are public HTTP endpoints. If not properly secured, they can be exploited. Here’s how to protect them:

1. Verify Webhook Signatures (CRITICAL)

Always verify that the webhook came from the trusted source using HMAC signatures.

JavaScript - HMAC Signature Verification const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { // Compute expected signature const hash = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); // Compare using timing-safe comparison return crypto.timingsSafeEqual( Buffer.from(hash), Buffer.from(signature) ); } // Usage const payload = JSON.parse(request.body); const signature = request.headers['x-webhook-signature']; const isValid = verifyWebhookSignature(payload, signature, WEBHOOK_SECRET); if (!isValid) { throw new Error('Webhook signature verification failed'); }

2. Use HTTPS Only

Always use HTTPS (not HTTP) for your webhook endpoint. This encrypts data in transit.

❌ Wrong: http://myapp.com/webhooks/stripe
✅ Correct: https://myapp.com/webhooks/stripe

3. Implement IP Whitelisting

Some providers publish their webhook IP ranges. Restrict requests to those IPs:

Node.js - IP Whitelist Middleware const ALLOWED_IPS = [ '34.235.140.0/24', // Stripe IP range '54.187.174.169', // GitHub '72.33.114.0/25', // Shopify ]; function ipWhitelistMiddleware(req, res, next) { const clientIP = req.ip; const isAllowed = ALLOWED_IPS.some(ip => { // Check if client IP matches allowed IP return ipInRange(clientIP, ip); }); if (!isAllowed) { return res.status(403).json({error: 'IP not allowed'}); } next(); } app.post('/webhooks/stripe', ipWhitelistMiddleware, handleWebhook);

4. Validate Event IDs (Idempotency)

Webhooks can be delivered multiple times. Use event IDs to detect duplicates:

Database Check - Prevent Duplicate Processing async function handleWebhook(event) { // Check if we've already processed this event const processed = await db.webhookEvents.findOne({ eventId: event.id }); if (processed) { console.log('Webhook already processed, skipping'); return; } // Process the webhook await processEvent(event); // Mark as processed await db.webhookEvents.create({ eventId: event.id, processedAt: new Date() }); }

5. Set Request Timeouts

Your webhook handler should respond quickly (ideally within 5-10 seconds):

Node.js - Webhook Timeout app.post('/webhooks/stripe', async (req, res) => { // Set timeout for this request req.setTimeout(10000, () => { res.status(408).json({error: 'Request timeout'}); }); try { // Quick response to acknowledge receipt res.status(200).json({received: true}); // Process webhook asynchronously await processWebhookAsync(req.body); } catch (error) { // Log error but don't fail the response console.error('Webhook processing error:', error); } });

6. Implement Rate Limiting

Prevent webhook endpoint from being flooded with requests:

Node.js - Rate Limiting const rateLimit = require('express-rate-limit'); const webhookLimiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 100, // 100 requests per minute message: 'Too many webhooks received' }); app.post('/webhooks/stripe', webhookLimiter, handleWebhook);

7. Log All Webhook Activity

Maintain detailed logs for debugging and security audits:

Node.js - Webhook Logging async function handleWebhook(req, res) { const startTime = Date.now(); const eventId = req.body.id; try { console.log(`[WEBHOOK] Received event: ${eventId}`); // Process webhook await processEvent(req.body); const duration = Date.now() - startTime; console.log(`[WEBHOOK] Processed event ${eventId} in ${duration}ms`); res.status(200).json({received: true}); } catch (error) { console.error(`[WEBHOOK ERROR] Event ${eventId}: ${error.message}`); res.status(500).json({error: error.message}); } }

Webhook Security Checklist

Security Verification Checklist:

  • ✓ Always verify webhook signatures using HMAC
  • ✓ Use HTTPS only (never HTTP)
  • ✓ Store webhook secrets in environment variables (not code)
  • ✓ Implement IP whitelisting if available
  • ✓ Prevent duplicate processing with event IDs
  • ✓ Set request timeouts (5-10 seconds)
  • ✓ Implement rate limiting
  • ✓ Log all webhook activity
  • ✓ Respond quickly (202 Accepted) then process async
  • ✓ Validate and sanitize all incoming data
  • ✓ Implement retry logic with exponential backoff
  • ✓ Monitor webhook failures and set up alerts

Troubleshooting Webhooks: Common Issues & Solutions

Problem 1: Webhook Not Firing

Possible Causes:

  • Event not configured in webhook settings
  • Webhook URL not accessible from internet
  • Firewall blocking incoming requests
  • DNS not resolving your domain
  • SSL certificate issues

Solutions:

  1. Verify webhook configuration in provider’s dashboard
  2. Check that endpoint URL is publicly accessible
  3. Test with curl: curl -X POST https://myapp.com/webhooks/test
  4. Use ngrok for local testing: ngrok http 3000
  5. Check firewall rules allow incoming traffic on port 443
  6. Verify SSL certificate is valid
  7. Check server logs for errors

Problem 2: Webhook Returns 500 Error

Possible Causes:

  • Code error in webhook handler
  • Database connection failure
  • Missing environment variable
  • Unhandled exception

Solution:

Node.js - Better Error Handling app.post('/webhooks/stripe', async (req, res) => { try { // Add logging before processing console.log('Webhook received:', req.body); // Validate input if (!req.body || !req.body.data) { return res.status(400).json({error: 'Invalid payload'}); } // Process webhook await processEvent(req.body); res.status(200).json({received: true}); } catch (error) { // Log the full error for debugging console.error('FULL ERROR:', error); console.error('Stack:', error.stack); // Return proper error response res.status(500).json({ error: error.message, timestamp: new Date().toISOString() }); } });

Problem 3: Duplicate Webhook Processing

Why It Happens:

Webhook providers retry failed requests. If you don’t acknowledge receipt (HTTP 200), they’ll send it again.

Solution:

Node.js - Idempotent Webhook Handler app.post('/webhooks/stripe', async (req, res) => { const eventId = req.body.id; try { // Check if already processed const exists = await db.collection('webhooks').findOne({ eventId: eventId }); if (exists) { console.log(`Event ${eventId} already processed`); return res.status(200).json({received: true}); } // Process the webhook await processEvent(req.body); // Mark as processed await db.collection('webhooks').insertOne({ eventId: eventId, processedAt: new Date(), status: 'success' }); res.status(200).json({received: true}); } catch (error) { console.error('Error:', error); res.status(500).json({error: error.message}); } });

Problem 4: Webhook Timeout

Issue:

Your webhook handler takes too long (>30 seconds), so webhook provider times out and retries.

Solution: Respond Immediately, Process Asynchronously

Node.js - Async Processing app.post('/webhooks/stripe', async (req, res) => { try { // Validate immediately verifySignature(req.body, req.headers['stripe-signature']); // IMPORTANT: Respond immediately with 202 Accepted res.status(202).json({received: true}); // Process asynchronously (don't await) processEventAsync(req.body).catch(error => { console.error('Async processing error:', error); }); } catch (error) { res.status(400).json({error: error.message}); } }); async function processEventAsync(event) { // This runs after response is sent await updateDatabase(event); await sendEmails(event); await triggerWorkflows(event); console.log('Event fully processed'); }

Problem 5: 403 Forbidden Errors

Causes:

  • IP not whitelisted
  • Authentication header missing
  • Wrong API key
  • Signature verification failing

Debugging Steps:

Node.js - Debug Signature Verification function debugSignatureVerification(payload, signature, secret) { console.log('=== SIGNATURE DEBUG ==='); console.log('Payload:', payload); console.log('Received signature:', signature); const computed = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); console.log('Computed signature:', computed); console.log('Match:', computed === signature); return computed === signature; }

Webhook Debugging Tools

ToolPurposeURL
Webhook.siteInspect all requests sent to your URLwebhook.site
RequestBinCapture and inspect webhooksrequestbin.com
Stripe CLITest Stripe webhooks locallystripe.com/docs/stripe-cli
ngrokExpose local server to internetngrok.com
Server LogsReview application logsYour app

How to Debug a Webhook

  1. Start your local server
  2. Run ngrok: ngrok http 3000 to get HTTPS URL
  3. Register ngrok URL in webhook settings
  4. Add detailed logging to your webhook handler
  5. Trigger test event
  6. Check console logs for errors
  7. View ngrok inspector dashboard to see requests
  8. Verify signature and payload

Webhook Tools & Providers

Webhook Management Platforms

PlatformBest ForKey FeaturesPricing
HookdeckReliable webhook deliveryRetry logic, routing, monitoringFree tier available
SvixEnterprise webhooksHMAC signatures, replay protection, API-firstFree tier + paid
ZapierNo-code automationConnect 1000+ appsFree + Premium
CourierNotification managementMulti-channel delivery, webhooksFree + Enterprise

Testing & Development Tools

  • Webhook.site: Free webhook inspection tool
  • ngrok: Expose local server to internet
  • Postman: Manual webhook testing
  • Stripe CLI: Stripe webhook testing
  • RequestBin: Capture webhook requests

Webhook Monitoring

  • Track webhook delivery success rates
  • Monitor response times
  • Alert on failures
  • Replay failed webhooks
  • View webhook logs

Frequently Asked Questions About Webhooks

What is a webhook in simple terms?

A webhook is an automated notification. Instead of constantly asking “did anything happen?”, a webhook lets the other application tell you when something happens. It’s like a doorbell for your app.

How do webhooks work?

You register a URL with a service (like Stripe). When an event occurs, Stripe sends an HTTP POST request to your URL with data about the event. Your app receives it and takes action.

Are webhooks real-time?

Yes, webhooks are real-time or near-real-time. The notification is sent immediately when the event happens, usually within milliseconds.

Are webhooks secure?

Webhooks can be secure if implemented properly. Use HMAC signature verification, HTTPS only, IP whitelisting, and validate all incoming data.

What’s the difference between webhooks and APIs?

Webhooks (push): Service sends data to you when event happens. APIs (pull): You ask service for data whenever you want. Webhooks are good for notifications, APIs are good for full data access.

Can I test webhooks locally?

Yes! Use ngrok to expose your local server to the internet: ngrok http 3000. Then register the ngrok URL with your webhook provider.

What happens if my webhook endpoint is down?

The webhook provider will retry. Most services retry with exponential backoff (1s, 2s, 4s, 8s, etc.) for up to 72 hours.

Can webhooks send data to multiple endpoints?

Each webhook goes to one endpoint. To send to multiple endpoints, either register multiple webhooks or have your endpoint forward to others.

How do I prevent duplicate webhook processing?

Use the webhook event ID to check if you’ve already processed it. Store processed event IDs in a database and skip duplicates.

What’s a webhook signature?

A webhook signature is an HMAC hash that verifies the webhook came from the trusted source. It prevents spoofing and man-in-the-middle attacks.

What’s a webhook endpoint?

A webhook endpoint is the URL where you want to receive webhooks. Example: https://myapp.com/webhooks/stripe

What’s webhook payload?

The payload is the data sent in the webhook request body. It’s usually JSON and contains details about the event (e.g., payment amount, customer email).

How quickly should I respond to a webhook?

You should respond with HTTP 200 within 5-30 seconds (depending on the provider). Process heavy operations asynchronously after responding.

Can I use webhooks without coding?

Yes! Services like Zapier let you connect apps and create webhook automations without writing code.

What if a webhook keeps failing?

Check your server logs, verify the signature, ensure your endpoint is accessible, check for timeouts, and verify all dependencies are working.

Conclusion: Master Webhooks for Real-Time Integrations

Webhooks are one of the most powerful tools in modern web development. They enable real-time, event-driven communication between applications without wasting resources on constant polling.

Key Takeaways:

  • Webhooks are HTTP callbacks that notify your app when something happens
  • They’re real-time, lightweight, and much more efficient than polling
  • Always verify webhook signatures using HMAC for security
  • Respond quickly (HTTP 200) and process heavy operations asynchronously
  • Use event IDs to prevent duplicate processing
  • Implement proper error handling, logging, and monitoring

Next Steps:

  1. Choose a service to integrate with (Stripe, GitHub, Shopify, etc.)
  2. Create a webhook endpoint in your preferred language (we showed 6!)
  3. Register the endpoint with the service provider
  4. Test locally with ngrok
  5. Deploy to production and monitor webhook delivery

Ready to implement webhooks? Start small with a test webhook, then expand to production. The code examples in this guide work with Stripe, but the principles apply to any webhook provider.

Have questions? Check the FAQ section above, or comment below. We’ll help you master webhooks!

Table of Contents

Hire top 1% global talent now

Related blogs

Text annotation is the process of adding structured labels entity tags, sentiment scores, intent classes, relational links, and grammatical markers

The Data Annotation Trends is defined by a single insight that the AI industry learned the hard way: model performance

Choosing the right Java web application framework is one of the most consequential technical decisions your team will make. It

Image annotation is the process of adding structured labels bounding boxes, polygons, segmentation masks, or keypoints to visual data so