What Are Webhooks? A Developer's Guide
Webhooks are one of the most common patterns in modern API development, yet they're often misunderstood. This guide covers everything you need to know — from the basics to production best practices.
Webhooks in a Nutshell
A webhook is an HTTP callback: when something happens in one system, it automatically sends an HTTP request to a URL you've configured in advance.
Think of it as the difference between:
- Polling (you ask): "Did anything happen?" → "No." → "How about now?" → "No." → "Now?" → "Yes!"
- Webhooks (they tell you): "Hey, something just happened. Here are the details."
How Webhooks Work
┌──────────┐ 1. Register URL ┌──────────────┐
│ Your │ ──────────────────── │ Webhook │
│ Server │ │ Provider │
│ │ ◄─────────────────── │ (e.g. │
│ │ 2. Event occurs, │ Stripe) │
│ │ POST to URL │ │
└──────────┘ └──────────────┘
- You register a URL with the webhook provider (e.g.,
https://api.example.com/webhooks/stripe) - An event occurs in the provider's system (e.g., a payment succeeds)
- The provider sends an HTTP POST request to your URL with event data
- Your server processes the event and returns a 2xx status code
- If delivery fails, the provider typically retries with exponential backoff
Webhooks vs. Polling vs. WebSockets
| Webhooks | Polling | WebSockets | |
|---|---|---|---|
| Direction | Server → Your Server | Your Server → Server | Bidirectional |
| Efficiency | High (event-driven) | Low (repeated requests) | High (persistent) |
| Real-time | Near real-time | Depends on interval | Real-time |
| Complexity | Medium | Low | High |
| Use case | Event notifications | Simple data fetching | Live interactions |
When to Use Webhooks
- Reacting to events in third-party services (payments, deployments, form submissions)
- Syncing data between systems
- Triggering workflows based on external events
- Any scenario where polling would waste resources
When NOT to Use Webhooks
- You need sub-second latency (use WebSockets)
- The data changes very frequently (use streaming)
- You control both systems (consider message queues)
Common Webhook Providers
Almost every modern SaaS app sends webhooks:
| Provider | Common Events |
|---|---|
| Stripe | payment_intent.succeeded, customer.created, invoice.paid |
| GitHub | push, pull_request, issues, release |
| Shopify | orders/create, products/update, app/uninstalled |
| Twilio | message.received, call.completed |
| Slack | message, reaction_added, channel_created |
| SendGrid | delivered, opened, bounced |
Implementing a Webhook Endpoint
Here's a basic webhook endpoint in different frameworks:
Node.js (Express)
import express from 'express'
const app = express()
// Important: use raw body for signature verification
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const event = JSON.parse(req.body.toString())
switch (event.type) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', event.data.object.id)
break
case 'customer.created':
console.log('New customer:', event.data.object.email)
break
default:
console.log('Unhandled event type:', event.type)
}
res.status(200).json({ received: true })
}
)
Python (FastAPI)
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhooks/stripe")
async def handle_webhook(request: Request):
payload = await request.json()
match payload["type"]:
case "payment_intent.succeeded":
print(f"Payment succeeded: {payload['data']['object']['id']}")
case "customer.created":
print(f"New customer: {payload['data']['object']['email']}")
case _:
print(f"Unhandled: {payload['type']}")
return {"received": True}
Nuxt.js (API Route)
export default defineEventHandler(async (event) => {
const body = await readBody(event)
switch (body.type) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', body.data.object.id)
break
case 'customer.created':
console.log('New customer:', body.data.object.email)
break
}
return { received: true }
})
Webhook Payload Anatomy
A typical webhook payload looks like:
{
"id": "evt_1234567890",
"type": "payment_intent.succeeded",
"created": 1708300800,
"data": {
"object": {
"id": "pi_abc123",
"amount": 2000,
"currency": "usd",
"status": "succeeded",
"customer": "cus_xyz789"
}
}
}
Key elements:
id— Unique event identifier (use for idempotency)type— What happened (use for routing)created— When the event occurred (use for ordering and replay detection)data— The actual event payload
Production Best Practices
1. Verify Signatures
Always verify that the webhook came from the expected sender:
import crypto from 'crypto'
function verifySignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}
2. Respond Quickly
Return 200 OK immediately, then process asynchronously:
app.post('/webhooks', async (req, res) => {
res.status(200).send('OK') // Respond immediately
await queue.add(req.body) // Process later
})
3. Handle Duplicates
Webhooks can be delivered more than once. Track processed event IDs:
const processed = new Set()
function handleWebhook(event) {
if (processed.has(event.id)) return
processed.add(event.id)
// ... handle event
}
4. Handle Failures Gracefully
If your endpoint returns a non-2xx status, most providers will retry. Make sure:
- Transient errors (DB timeout, network issue) return 5xx → provider retries
- Permanent errors (invalid payload) return 4xx → provider stops retrying
5. Use a Webhook Gateway
Instead of building all this infrastructure yourself, use a webhook gateway like Mitte that provides:
- Automatic retries with configurable policies
- Real-time delivery logs with full request/response data
- HMAC signature verification
- Payload transforms
- AI-powered error analysis
Testing Webhooks
Testing webhooks is uniquely challenging because you need a publicly accessible URL during development.
Options:
- Webhook gateway (Mitte) — Persistent URL with full logging
- Tunneling (ngrok) — Expose localhost temporarily
- Provider CLI (Stripe CLI) — Provider-specific testing tools
- Unit tests — Mock the webhook payload locally
Read our detailed guide: How to Test Webhooks Locally →
Summary
Webhooks are the backbone of event-driven integrations. To use them effectively:
- ✅ Register your URL with the provider
- ✅ Verify signatures on every request
- ✅ Return 200 quickly, process async
- ✅ Handle duplicates with idempotency keys
- ✅ Use a webhook gateway for production reliability
Ready to receive webhooks the right way? Create your first Mitte endpoint for free →
Top 5 Webhook.site Alternatives for Developers in 2026
Need more than Webhook.site offers? Compare the best alternatives for webhook testing and delivery — Mitte, RequestBin, Beeceptor, Pipedream, and hookbin.
What is MCP? How the Model Context Protocol Changes Developer Workflows
Learn what the Model Context Protocol (MCP) is, how it works, and why it matters for developers. See real examples of using MCP to manage webhooks from your AI assistant.