What Are Webhooks? The Complete Guide with Examples
A webhook is an automated message sent from one application to another when a specific event occurs. Instead of constantly asking "did anything happen?" (polling), webhooks push data to you in real-time the moment something happens.
Think of it like this: rather than repeatedly calling a store to check if your order shipped, the store calls you when it ships. That's the webhook model.
How Webhooks Work
Webhooks follow a simple pattern:
- You provide a URL — Your application exposes an HTTP endpoint (the "webhook URL") that can receive POST requests
- An event occurs — Something happens in the source application (payment received, form submitted, file uploaded)
- Data is pushed to you — The source application sends an HTTP POST request to your URL with event data as JSON
┌─────────────────┐ Event occurs ┌─────────────────┐
│ │ │ │
│ Stripe/GitHub │ ──── HTTP POST + JSON ────▶ │ Your Webhook │
│ Shopify/Slack │ │ Endpoint │
│ │ │ │
└─────────────────┘ └─────────────────┘
Here's what a typical webhook payload looks like (from Stripe):
{
"id": "evt_1234567890",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_abc123",
"amount": 2000,
"currency": "usd",
"customer": "cus_xyz789"
}
},
"created": 1699900000
}
Your endpoint receives this data and can immediately take action — update a database, send an email, trigger a workflow.
Webhooks vs APIs: What's the Difference?
Both webhooks and APIs enable applications to communicate, but they work in opposite directions:
| Aspect | API (Pull) | Webhook (Push) |
|---|---|---|
| Direction | You request data | Data is sent to you |
| Trigger | Your application initiates | External event initiates |
| Timing | On-demand | Real-time |
| Efficiency | May require polling | No wasted requests |
| Use case | Fetching data, CRUD operations | Event notifications |
The Polling Problem
Without webhooks, you'd need to repeatedly call an API to check for updates:
// ❌ Polling - inefficient, wasteful
setInterval(async () => {
const orders = await fetch('/api/orders?status=new');
// Check if anything changed...
}, 5000); // Every 5 seconds = 17,280 requests/day
With webhooks, you receive data only when something happens:
// ✅ Webhook - efficient, real-time
app.post('/webhooks/orders', (req, res) => {
const order = req.body;
// Process immediately when event occurs
res.status(200).send('OK');
});
Real-World Webhook Examples
Webhooks power countless integrations across the web:
Payment Processing
- Stripe sends webhooks when payments succeed, fail, or dispute
- PayPal notifies you of completed transactions
- Shopify alerts your system about new orders
Developer Tools
- GitHub triggers webhooks on push, pull request, or issue events
- GitLab notifies CI/CD pipelines of code changes
- Jira sends updates when tickets are created or modified
Communication
- Slack sends webhooks for messages, commands, and events
- Discord notifies bots of user interactions
- Twilio pushes incoming SMS and call events
Business Automation
- Mailchimp sends subscriber activity webhooks
- Typeform notifies you of form submissions
- Calendly alerts you when meetings are scheduled
Webhook Payload Structure
Most webhooks send JSON data via HTTP POST. A typical payload includes:
{
"event": "order.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"id": "order_123",
"customer_email": "[email protected]",
"total": 99.99,
"items": [
{ "name": "Widget", "quantity": 2, "price": 49.99 }
]
},
"signature": "sha256=abc123..."
}
Key components:
- Event type — What happened (e.g.,
order.created,payment.failed) - Timestamp — When the event occurred
- Data — The actual payload with event details
- Signature — Cryptographic proof the request is authentic
Webhook Security Best Practices
Since webhooks are publicly accessible endpoints, security is critical:
1. Verify Signatures
Most webhook providers sign requests with HMAC. Always verify:
import crypto from 'crypto';
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
2. Use HTTPS
Always use HTTPS endpoints. Never accept webhooks over plain HTTP.
3. Validate Timestamps
Reject old requests to prevent replay attacks:
const timestamp = req.headers['x-webhook-timestamp'];
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
if (new Date(timestamp).getTime() < fiveMinutesAgo) {
return res.status(401).send('Request too old');
}
4. Respond Quickly
Return a 200 status immediately, then process asynchronously:
app.post('/webhook', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process in background
await processWebhookAsync(req.body);
});
5. Handle Retries
Webhook providers retry failed deliveries. Make your handler idempotent:
app.post('/webhook', async (req, res) => {
const eventId = req.body.id;
// Check if already processed
const existing = await db.findOne('processed_events', { eventId });
if (existing) {
return res.status(200).send('Already processed');
}
// Process and record
await processEvent(req.body);
await db.insertOne('processed_events', { eventId, processedAt: new Date() });
res.status(200).send('OK');
});
Building a Webhook Endpoint
Here's a complete webhook handler using Codehooks.io:
import { app, Datastore } from 'codehooks-js';
import crypto from 'crypto';
// Webhook endpoint
app.post('/webhooks/orders', async (req, res) => {
// 1. Verify signature
const signature = req.headers['x-webhook-signature'];
const isValid = verifySignature(req.rawBody, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Parse event
const { event, data } = req.body;
// 3. Handle event types
switch (event) {
case 'order.created':
await handleNewOrder(data);
break;
case 'order.shipped':
await handleShipment(data);
break;
case 'order.cancelled':
await handleCancellation(data);
break;
}
// 4. Acknowledge receipt
res.status(200).json({ received: true });
});
async function handleNewOrder(order) {
const conn = await Datastore.open();
await conn.insertOne('orders', {
...order,
receivedAt: new Date().toISOString()
});
// Trigger downstream actions
await sendConfirmationEmail(order.customer_email);
await updateInventory(order.items);
}
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature || ''),
Buffer.from(expected)
);
}
export default app.init();
Deploy in seconds:
coho deploy
Sending Webhooks (Outgoing)
You can also send webhooks from your application to notify others:
For a complete webhook delivery system with retries, queues, and HMAC signing, use our ready-made template: coho create myapp --template webhook-delivery or see the Build a Webhook Delivery System guide.
import { app, Datastore } from 'codehooks-js';
import crypto from 'crypto';
async function sendWebhook(url, event, data, secret) {
const payload = JSON.stringify({ event, data, timestamp: new Date().toISOString() });
// Create signature
const signature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': signature
},
body: payload
});
return response.ok;
}
// Example: Notify subscribers when an order is created
app.post('/orders', async (req, res) => {
const order = await createOrder(req.body);
// Get all webhook subscribers
const conn = await Datastore.open();
const subscribers = await conn.getMany('webhook_subscribers', {
event: 'order.created'
}).toArray();
// Send webhooks to all subscribers
for (const sub of subscribers) {
await sendWebhook(sub.url, 'order.created', order, sub.secret);
}
res.json(order);
});
export default app.init();
Testing Webhooks
With Codehooks.io, you don't need ngrok or local tunneling. Just run coho deploy and your webhook endpoint is live instantly. Use coho info to get your public URL.
Local Development
If you're running a local server, use tools like ngrok or localtunnel to expose it:
ngrok http 3000
# Gives you: https://abc123.ngrok.io
Webhook Testing Services
- Webhook.site — Inspect incoming webhooks
- RequestBin — Debug webhook payloads
- Hookdeck Console — Test and replay webhooks
Stripe CLI Example
stripe listen --forward-to localhost:3000/webhooks/stripe
stripe trigger payment_intent.succeeded
Common Webhook Patterns
Event Queuing
For high-volume webhooks, queue events for processing with a worker:
import { app, Datastore } from 'codehooks-js';
// Receive webhook and add to queue
app.post('/webhook', async (req, res) => {
// Quick acknowledgment
res.status(200).send('OK');
// Add to queue for async processing
const conn = await Datastore.open();
await conn.enqueue('webhook-events', req.body);
});
// Queue worker processes events asynchronously
app.worker('webhook-events', async (req, res) => {
const event = req.body.payload;
console.log('Processing webhook:', event.type);
// Handle the event (database updates, notifications, etc.)
await processWebhookEvent(event);
res.end();
});
export default app.init();
For complex multi-step processing, use the Workflow API:
import { app, Datastore } from 'codehooks-js';
// Define a workflow for processing webhooks
const orderWorkflow = app.createWorkflow({
name: 'process-order',
steps: {
validate: async (state, goto) => {
// Validate the order
state.validated = true;
return goto('notify', state);
},
notify: async (state, goto) => {
// Send notifications
await sendNotification(state.orderId);
return goto('sync', state);
},
sync: async (state, goto) => {
// Sync with external systems
await syncOrder(state.orderId);
return goto('$end', state);
}
}
});
app.post('/webhook', async (req, res) => {
res.status(200).send('OK');
// Start workflow for complex processing
await orderWorkflow.start({
orderId: req.body.data.id
});
});
export default app.init();
Webhook Retries with Exponential Backoff
When sending webhooks, retry with increasing delays:
async function sendWithRetry(url, payload, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (response.ok) return true;
} catch (error) {
console.log(`Attempt ${attempt + 1} failed`);
}
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
await sleep(Math.pow(2, attempt) * 1000);
}
return false;
}
Ready-to-Deploy Webhook Templates
Skip the boilerplate and deploy production-ready webhook handlers in minutes using Codehooks.io templates:
Receiving Webhooks (Inbound)
| Template | Command | Description |
|---|---|---|
| Stripe | coho create myapp --template webhook-stripe-minimal | Payment events with signature verification |
| GitHub | coho create myapp --template webhook-github-minimal | Push, PR, and issue events with HMAC-SHA256 |
| Shopify | coho create myapp --template webhook-shopify-minimal | Orders, products, customers with HMAC verification |
| Slack | coho create myapp --template webhook-slack-minimal | Events and commands with signature verification |
| Discord | coho create myapp --template webhook-discord-minimal | Slash commands with Ed25519 verification |
| Twilio | coho create myapp --template webhook-twilio-minimal | SMS and voice events with TwiML responses |
| Clerk | coho create myapp --template webhook-clerk-minimal | Auth events (signup, login) with Svix verification |
Sending Webhooks (Outbound)
| Template | Command | Description |
|---|---|---|
| Webhook Delivery | coho create myapp --template webhook-delivery | Full webhook system with retries, queues, HMAC signing |
All templates include signature verification, error handling, and are ready for production use.
Getting Started with Webhooks
Ready to implement webhooks? Here are integration guides for popular services:
- Stripe Webhooks — Payment events
- GitHub Webhooks — Repository events
- Shopify Webhooks — E-commerce events
- Slack Bot Webhooks — Chat commands and events
- Discord Bot Webhooks — Bot interactions
- Jira Webhooks — Issue tracking events
Or build your own webhook delivery system:
Webhook FAQ
Common questions about webhooks and how they work
What is a webhook in simple terms?
What is the difference between a webhook and an API?
How do webhooks work?
Are webhooks secure?
What is a webhook URL?
https://yourapp.com/webhooks/stripe) that accepts POST requests. The webhook provider sends event data to this URL.What happens if my webhook endpoint is down?
What is webhook signature verification?
Can I send webhooks from my own application?
What format do webhooks use?
How do I test webhooks locally?
ngrok http 3000 to get a public URL, then configure the webhook provider to send events there. Tools like Webhook.site let you inspect payloads without code.