Introduction
Webhooks are the universal connector for modern applications. They're HTTP callbacks that enable real-time, event-driven communication between services—no polling, no delays, just instant notifications when something happens.
In the context of notification infrastructure, webhooks solve a critical problem: What do you do when there's no native integration for your service?
Whether you're integrating with a custom CRM, a legacy internal tool, a third-party analytics platform, or your own microservices, webhooks provide the flexibility to send notifications anywhere via standard HTTP.
By the end of this guide, you'll understand:
- ✅ What webhooks are and why they matter
- ✅ How to set up secure webhook integrations
- ✅ Real-world webhook use cases and patterns
- ✅ Security best practices (HMAC verification, replay protection)
- ✅ Advanced webhook architectures (fan-out, aggregation)
- ✅ Troubleshooting common webhook issues
- ✅ Testing and debugging strategies
Estimated time: 20 minutes Difficulty: Intermediate Prerequisites: Basic HTTP/REST API knowledge, Node.js or similar
What Are Webhooks?
A webhook is an HTTP POST request sent automatically when an event occurs. Unlike traditional APIs where you request data (polling), webhooks push data to you in real-time.
Traditional Polling (Inefficient):
Your App: "Any new notifications?" → API
(wait 5 seconds)
Your App: "Any new notifications?" → API
(wait 5 seconds)
Your App: "Any new notifications?" → API
Webhooks (Efficient):
Event occurs → Webhook fires → Your App receives notification instantly
Why Webhooks Matter for Notifications
Webhooks are perfect for notifications because:
- Real-Time Delivery - No delay between event and notification
- Universal Integration - Works with any HTTP endpoint
- Event-Driven - Only fires when something happens
- Scalable - Server pushes to you (no constant polling overhead)
- Flexible - Transform and route data however you need
Common Webhook Use Cases
- Custom CRM/ERP Integration - Send customer events to Salesforce, HubSpot, or proprietary systems
- Internal Dashboards - Push metrics and alerts to monitoring tools
- Analytics Platforms - Track notification delivery to Mixpanel, Amplitude, Segment
- Legacy Systems - Integrate with older systems that don't support modern APIs
- Microservices - Notify other services in your architecture
- Zapier/Make Workflows - Trigger automation workflows
- Slack/Discord Bots - Send notifications to custom Slack channels or Discord servers
- Data Warehouses - Stream events to Snowflake, BigQuery, Redshift
For a broader understanding of notification infrastructure, see our guide on What Is Notification Infrastructure.
Webhook Architecture in NotiGrid
Here's how webhooks work in NotiGrid's notification flow:
1. Event Occurs (user.signup, order.completed, etc.)
↓
2. NotiGrid Workflow Engine processes event
↓
3. Webhook channel configured for event
↓
4. NotiGrid sends HTTP POST to your endpoint
↓
5. Your endpoint processes notification
↓
6. Endpoint returns 200 OK (success)
↓
7. NotiGrid logs delivery status
If your endpoint fails:
- NotiGrid automatically retries with exponential backoff
- Logs error details for debugging
- Moves to Dead Letter Queue after max retries
Learn more about building resilient multi-channel workflows in our Multi-Channel Notification System Guide.
Setting Up a Webhook Integration
Step 1: Create the Webhook in NotiGrid Dashboard
Navigate to Integrations → Add New → Webhooks and configure:
{
"type": "webhook",
"name": "Custom CRM Integration",
"config": {
"url": "https://your-service.com/api/notifications",
"method": "POST",
"headers": {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"X-Service-ID": "crm-prod"
},
"timeout": 10000,
"retries": 3
}
}Configuration Options:
url- Your endpoint URL (must be HTTPS)method- HTTP method (POST, PUT, PATCH)headers- Custom headers (auth, content-type, etc.)timeout- Request timeout in milliseconds (default: 10s)retries- Number of retry attempts (default: 3)
Step 2: Create a Notification Channel
Link your webhook to a notification channel:
import { NotiGrid } from '@notigrid/node'
const client = new NotiGrid(process.env.NOTIGRID_API_KEY)
await client.channels.create({
id: 'crm-customer-created',
name: 'CRM Customer Created Event',
provider: 'webhook',
integrationId: 'custom-crm-integration',
template: {
body: JSON.stringify({
event: '{{event}}',
customer_id: '{{customerId}}',
email: '{{email}}',
created_at: '{{timestamp}}'
})
}
})Step 3: Build Your Webhook Endpoint
Create an endpoint to receive webhook calls. Here's a production-ready Express.js example:
import express from 'express'
import crypto from 'crypto'
const app = express()
// Parse JSON payloads
app.use(express.json())
app.post('/api/notifications', async (req, res) => {
try {
// 1. Verify webhook signature (CRITICAL for security)
if (!verifyWebhookSignature(req)) {
return res.status(401).json({ error: 'Invalid signature' })
}
// 2. Extract payload
const { event, timestamp, data } = req.body
// 3. Process notification
await processNotification(event, data)
// 4. Return 200 OK immediately (acknowledge receipt)
res.status(200).json({ received: true })
} catch (error) {
console.error('Webhook processing error:', error)
// Return 5xx for retryable errors
res.status(500).json({ error: 'Internal server error' })
}
})
function verifyWebhookSignature(req: express.Request): boolean {
const signature = req.headers['x-notigrid-signature'] as string
const timestamp = req.headers['x-notigrid-timestamp'] as string
if (!signature || !timestamp) return false
// Prevent replay attacks (reject requests older than 5 minutes)
const age = Date.now() - parseInt(timestamp)
if (age > 5 * 60 * 1000) return false
// Verify HMAC signature
const payload = timestamp + '.' + JSON.stringify(req.body)
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}
async function processNotification(event: string, data: any) {
// Your business logic here
console.log(`Processing ${event}:`, data)
// Examples:
// - Update database
// - Call third-party API
// - Trigger workflow
// - Send to message queue
}
app.listen(3000, () => {
console.log('Webhook endpoint listening on port 3000')
})Step 4: Test Your Webhook
Use NotiGrid's webhook testing tool to send test payloads:
await client.notify({
channelId: 'crm-customer-created',
variables: {
event: 'customer.created',
customerId: 'cust_12345',
email: 'john@example.com',
timestamp: new Date().toISOString()
}
})Check your endpoint logs to verify receipt!
Webhook Payload Structure
NotiGrid sends webhooks with this standard structure:
interface WebhookPayload {
// Event metadata
id: string // Unique webhook delivery ID
event: string // Event type (e.g., "notification.sent")
timestamp: string // ISO 8601 timestamp
attempt: number // Retry attempt number (1-5)
// Notification data
data: {
notificationId: string // NotiGrid notification ID
channelId: string // Channel that triggered webhook
template: string // Template used
variables: Record<string, any> // Template variables
to?: string // Recipient (if applicable)
status: string // Delivery status
provider?: string // Provider used (if multi-channel)
}
// Metadata
metadata?: Record<string, any> // Custom metadata you provided
}Example Payload:
{
"id": "wh_7Kd9sL2mP4nQ",
"event": "notification.sent",
"timestamp": "2025-12-01T15:30:00Z",
"attempt": 1,
"data": {
"notificationId": "notif_abc123",
"channelId": "order-confirmation",
"template": "order-receipt",
"variables": {
"orderId": "ORD-5678",
"customerName": "Sarah Chen",
"total": 149.99,
"items": 3
},
"to": "sarah@example.com",
"status": "delivered",
"provider": "email-ses"
},
"metadata": {
"source": "checkout-service",
"environment": "production"
}
}Security Best Practices
1. Always Verify Webhook Signatures
Why: Anyone can send a POST request to your endpoint. Signature verification proves the request came from NotiGrid.
How it works:
- NotiGrid generates an HMAC signature using your secret key
- Signature is sent in
x-notigrid-signatureheader - You recreate the signature and compare
Implementation:
import crypto from 'crypto'
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
)
}Learn more about HMAC signatures in the RFC 2104 spec.
2. Prevent Replay Attacks
Verify the timestamp to reject old requests:
function isRecentRequest(timestamp: string, maxAgeSeconds: number = 300): boolean {
const requestTime = new Date(timestamp).getTime()
const now = Date.now()
const age = (now - requestTime) / 1000
return age <= maxAgeSeconds
}3. Use HTTPS Only
Never use HTTP endpoints for webhooks:
- ❌
http://api.example.com/webhooks(unencrypted) - ✅
https://api.example.com/webhooks(encrypted)
NotiGrid enforces HTTPS for all webhook URLs. Learn more about HTTPS/TLS.
4. Implement Rate Limiting
Protect your endpoint from abuse:
import rateLimit from 'express-rate-limit'
const webhookLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1000, // 1000 requests per window
message: 'Too many webhook requests',
standardHeaders: true,
legacyHeaders: false,
})
app.use('/api/notifications', webhookLimiter)5. Validate Payload Schema
Always validate incoming data:
import Joi from 'joi'
const webhookSchema = Joi.object({
id: Joi.string().required(),
event: Joi.string().required(),
timestamp: Joi.string().isoDate().required(),
attempt: Joi.number().min(1).max(5).required(),
data: Joi.object().required()
})
function validateWebhook(payload: any): boolean {
const { error } = webhookSchema.validate(payload)
return !error
}6. Use IP Whitelisting (Optional)
For extra security, whitelist NotiGrid's IP ranges:
const NOTIGRID_IPS = [
'52.45.123.0/24',
'54.123.45.0/24'
// Get current IPs from NotiGrid dashboard
]
function isAllowedIP(ip: string): boolean {
return NOTIGRID_IPS.some(range => ipInRange(ip, range))
}Advanced Webhook Patterns
Pattern 1: Fan-Out (One Event → Multiple Endpoints)
Send the same event to multiple services:
const endpoints = [
'https://analytics.company.com/events',
'https://crm.company.com/webhooks',
'https://slack-bot.company.com/notify'
]
async function fanOutWebhook(payload: any) {
await Promise.all(
endpoints.map(url =>
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
)
)
}Pattern 2: Payload Transformation
Transform webhook data before sending to your service:
function transformForCRM(notigridPayload: any) {
return {
// CRM expects different field names
contact_id: notigridPayload.data.variables.customerId,
contact_email: notigridPayload.data.to,
event_type: notigridPayload.event,
event_date: notigridPayload.timestamp,
metadata: {
notigrid_id: notigridPayload.id,
notification_channel: notigridPayload.data.channelId
}
}
}Pattern 3: Webhook Aggregation
Batch multiple events before sending:
const eventBatch: any[] = []
const BATCH_SIZE = 10
const BATCH_TIMEOUT = 5000 // 5 seconds
function addToBatch(event: any) {
eventBatch.push(event)
if (eventBatch.length >= BATCH_SIZE) {
flushBatch()
}
}
function flushBatch() {
if (eventBatch.length === 0) return
const batch = [...eventBatch]
eventBatch.length = 0
sendBatchedWebhook(batch)
}
// Flush on timeout
setInterval(flushBatch, BATCH_TIMEOUT)Pattern 4: Asynchronous Processing
Use a queue for high-volume webhooks:
import { Queue } from 'bull'
const webhookQueue = new Queue('webhooks', {
redis: process.env.REDIS_URL
})
// Receive webhook → add to queue → return 200 immediately
app.post('/api/notifications', async (req, res) => {
await webhookQueue.add('process', req.body)
res.status(200).json({ queued: true })
})
// Process webhooks asynchronously
webhookQueue.process('process', async (job) => {
await processNotification(job.data)
})Retry Logic and Error Handling
How NotiGrid Retries Work
NotiGrid automatically retries failed webhooks with exponential backoff:
| Attempt | Delay | Status Codes That Trigger Retry |
|---|---|---|
| 1 | Immediate | - |
| 2 | 1 second | 408, 429, 500, 502, 503, 504 |
| 3 | 5 seconds | 408, 429, 500, 502, 503, 504 |
| 4 | 15 seconds | 408, 429, 500, 502, 503, 504 |
| 5 (final) | 60 seconds | 408, 429, 500, 502, 503, 504 |
After 5 failed attempts, the webhook is moved to the Dead Letter Queue for manual review.
HTTP Status Codes Matter
Your endpoint should return appropriate status codes:
2xx Success - No retry
200 OK- Webhook processed successfully201 Created- Resource created from webhook202 Accepted- Webhook queued for async processing
4xx Client Errors - No retry (permanent failure)
400 Bad Request- Invalid payload401 Unauthorized- Invalid signature404 Not Found- Endpoint doesn't exist422 Unprocessable Entity- Valid JSON but business logic error
5xx Server Errors - Will retry (temporary failure)
500 Internal Server Error- Unexpected error502 Bad Gateway- Gateway/proxy error503 Service Unavailable- Temporarily down504 Gateway Timeout- Request timed out
Example Error Handling:
app.post('/api/notifications', async (req, res) => {
try {
// Validation error → 400 (no retry)
if (!req.body.event) {
return res.status(400).json({ error: 'Missing event field' })
}
// Auth error → 401 (no retry)
if (!verifySignature(req)) {
return res.status(401).json({ error: 'Invalid signature' })
}
// Business logic error → 422 (no retry)
if (!isValidCustomer(req.body.data.customerId)) {
return res.status(422).json({ error: 'Unknown customer' })
}
// Process webhook
await processNotification(req.body)
// Success → 200 (no retry)
return res.status(200).json({ success: true })
} catch (error) {
console.error('Webhook error:', error)
// Unknown error → 500 (will retry)
return res.status(500).json({ error: 'Internal error' })
}
})Real-World Use Cases
Use Case 1: Salesforce CRM Integration
Send customer events to Salesforce:
import jsforce from 'jsforce'
const sfConnection = new jsforce.Connection({
loginUrl: process.env.SALESFORCE_URL
})
await sfConnection.login(
process.env.SALESFORCE_USERNAME!,
process.env.SALESFORCE_PASSWORD!
)
app.post('/webhooks/salesforce', async (req, res) => {
const { event, data } = req.body
if (event === 'customer.created') {
// Create Lead in Salesforce
await sfConnection.sobject('Lead').create({
FirstName: data.variables.firstName,
LastName: data.variables.lastName,
Email: data.variables.email,
Company: data.variables.company,
LeadSource: 'NotiGrid Webhook'
})
}
res.status(200).json({ success: true })
})Use Case 2: Analytics Tracking (Segment)
Track notification events in Segment:
import Analytics from 'analytics-node'
const analytics = new Analytics(process.env.SEGMENT_WRITE_KEY!)
app.post('/webhooks/segment', async (req, res) => {
const { event, data } = req.body
analytics.track({
userId: data.variables.userId,
event: 'Notification Sent',
properties: {
channel: data.channelId,
provider: data.provider,
template: data.template,
notification_id: data.notificationId
},
timestamp: new Date(req.body.timestamp)
})
res.status(200).json({ tracked: true })
})Use Case 3: Internal Dashboard Alerts
Send real-time alerts to your monitoring dashboard:
import { WebSocket } from 'ws'
const dashboardWS = new WebSocket(process.env.DASHBOARD_WS_URL!)
app.post('/webhooks/dashboard', async (req, res) => {
const { event, data } = req.body
// Push to dashboard via WebSocket
dashboardWS.send(JSON.stringify({
type: 'notification_event',
severity: data.variables.severity || 'info',
message: data.variables.message,
timestamp: req.body.timestamp,
metadata: data.variables
}))
res.status(200).json({ sent: true })
})Use Case 4: Zapier Integration
Trigger Zapier workflows from webhooks:
app.post('/webhooks/zapier', async (req, res) => {
const { event, data } = req.body
// Forward to Zapier webhook URL
await fetch(process.env.ZAPIER_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event_type: event,
notification_id: data.notificationId,
channel: data.channelId,
variables: data.variables,
timestamp: req.body.timestamp
})
})
res.status(200).json({ forwarded: true })
})Troubleshooting Common Issues
Issue 1: Webhooks Not Being Received
Symptoms: NotiGrid shows webhooks as sent, but your endpoint never receives them
Causes:
- Firewall blocking NotiGrid's IP addresses
- Endpoint URL incorrect or DNS not resolving
- SSL/TLS certificate issues
- Endpoint not listening on correct port
Solutions:
- Verify endpoint URL in browser or with curl
- Check firewall rules and whitelist NotiGrid IPs
- Ensure SSL certificate is valid (use SSL Labs)
- Test with ngrok for local development:
ngrok http 3000 - Check server logs for incoming requests
- Use webhook testing tools like webhook.site
Issue 2: Signature Verification Failing
Symptoms: All webhooks rejected with 401 Unauthorized
Causes:
- Wrong webhook secret
- Incorrect signature calculation
- Character encoding issues
- Timestamp not included in signature
Solutions:
// Debug signature calculation
function debugSignature(req: express.Request) {
const receivedSignature = req.headers['x-notigrid-signature']
const timestamp = req.headers['x-notigrid-timestamp']
const payload = JSON.stringify(req.body)
console.log('Received signature:', receivedSignature)
console.log('Timestamp:', timestamp)
console.log('Payload:', payload)
const calculated = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(timestamp + '.' + payload)
.digest('hex')
console.log('Calculated signature:', calculated)
console.log('Match:', receivedSignature === calculated)
}Issue 3: Webhooks Timing Out
Symptoms: NotiGrid shows timeout errors, retries eventually succeed
Causes:
- Endpoint processing takes too long (>10 seconds)
- Synchronous processing of heavy operations
- Database queries blocking response
- External API calls in webhook handler
Solutions:
- Return 200 immediately, process asynchronously
- Use message queues (Bull, SQS, RabbitMQ)
- Optimize database queries
- Move heavy processing to background jobs
// Bad: Synchronous processing
app.post('/webhook', async (req, res) => {
await slowDatabaseOperation() // 5 seconds
await callExternalAPI() // 3 seconds
res.status(200).send('OK') // Times out!
})
// Good: Async processing
app.post('/webhook', async (req, res) => {
await queue.add('process-webhook', req.body)
res.status(200).send('OK') // Returns immediately
})Issue 4: Duplicate Webhook Deliveries
Symptoms: Same webhook received multiple times
Causes:
- Endpoint returned 5xx, NotiGrid retried
- Endpoint timeout, NotiGrid retried
- Race condition in endpoint code
Solutions:
- Implement idempotency using webhook ID
- Store processed webhook IDs in cache/database
- Return 200 quickly to prevent timeouts
import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL)
app.post('/webhook', async (req, res) => {
const webhookId = req.body.id
// Check if already processed
const processed = await redis.get(`webhook:${webhookId}`)
if (processed) {
return res.status(200).json({ duplicate: true })
}
// Process webhook
await processNotification(req.body)
// Mark as processed (expire after 24 hours)
await redis.setex(`webhook:${webhookId}`, 86400, '1')
res.status(200).json({ success: true })
})Issue 5: Payload Parsing Errors
Symptoms: 400 errors, "Unexpected token" in logs
Causes:
- Missing
express.json()middleware - Wrong Content-Type header
- Malformed JSON
Solutions:
// Ensure JSON parsing middleware
app.use(express.json())
// Add error handling for JSON parsing
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && 'body' in err) {
return res.status(400).json({ error: 'Invalid JSON' })
}
next(err)
})Testing and Debugging
Local Development with ngrok
Test webhooks locally without deploying:
# Install ngrok
npm install -g ngrok
# Start your local server
npm run dev # Running on http://localhost:3000
# Create tunnel
ngrok http 3000
# Use the HTTPS URL in NotiGrid dashboard
# https://abc123.ngrok.io/api/notificationsWebhook Testing Tools
1. webhook.site - Inspect incoming webhooks
# Get a unique URL
curl https://webhook.site/token
# Configure in NotiGrid dashboard
# View requests in browser2. RequestBin - Similar to webhook.site
# Visit https://requestbin.com/
# Create a bin
# Use the URL for testing3. cURL Testing - Simulate webhooks manually
curl -X POST https://your-endpoint.com/webhooks \
-H "Content-Type: application/json" \
-H "X-NotiGrid-Signature: test-signature" \
-H "X-NotiGrid-Timestamp: $(date -u +%s)000" \
-d '{
"id": "wh_test123",
"event": "notification.sent",
"timestamp": "2025-12-01T10:00:00Z",
"attempt": 1,
"data": {
"notificationId": "test",
"channelId": "test-channel"
}
}'Monitoring and Observability
Track webhook health in production:
import prometheus from 'prom-client'
const webhookCounter = new prometheus.Counter({
name: 'webhooks_received_total',
help: 'Total webhooks received',
labelNames: ['event', 'status']
})
const webhookDuration = new prometheus.Histogram({
name: 'webhook_processing_duration_seconds',
help: 'Webhook processing time',
labelNames: ['event']
})
app.post('/webhook', async (req, res) => {
const start = Date.now()
const event = req.body.event
try {
await processNotification(req.body)
webhookCounter.inc({ event, status: 'success' })
res.status(200).send('OK')
} catch (error) {
webhookCounter.inc({ event, status: 'error' })
res.status(500).send('Error')
} finally {
const duration = (Date.now() - start) / 1000
webhookDuration.observe({ event }, duration)
}
})Frequently Asked Questions
How do I secure my webhook endpoint?
Always implement these security measures:
- Verify HMAC signatures - Ensures requests come from NotiGrid
- Use HTTPS only - Encrypts data in transit
- Validate timestamps - Prevents replay attacks (reject requests older than 5 minutes)
- Implement rate limiting - Protects against DoS attacks
- Whitelist IPs - Optional extra layer of security
- Validate payload schema - Prevents malformed data from breaking your app
See the Security Best Practices section above for implementation details.
What happens if my endpoint is down?
NotiGrid automatically retries failed webhooks:
- Retries up to 5 times with exponential backoff (1s, 5s, 15s, 60s)
- After 5 failures, moves to Dead Letter Queue
- You can manually retry from the dashboard
- Logs all attempts with error details
Configure retry behavior in your webhook settings.
Can I send webhooks to multiple endpoints?
Yes! You have two options:
- Create multiple webhook integrations - Configure each as a separate integration in NotiGrid
- Fan-out pattern - One endpoint receives webhooks and forwards to multiple services (see Advanced Patterns section)
How do I test webhooks locally?
Use ngrok or localtunnel to expose your local server:
# With ngrok
ngrok http 3000
# With localtunnel
lt --port 3000Then use the HTTPS URL in your NotiGrid webhook configuration. Both tools provide a public URL that tunnels to your localhost.
What's the difference between webhooks and polling?
Webhooks (Push):
- Server sends data to you when events occur
- Real-time, instant notifications
- Efficient (only fires when needed)
- Requires publicly accessible endpoint
Polling (Pull):
- You request data from server repeatedly
- Delayed (depends on polling interval)
- Inefficient (wastes resources checking for updates)
- Works behind firewalls
Webhooks are significantly more efficient for real-time notifications. Learn more about HTTP and event-driven architecture.
How do I handle webhook retries idempotently?
Use the webhook id field to track processed webhooks:
// Store processed IDs in Redis with 24-hour expiry
const processed = await redis.get(`webhook:${webhookId}`)
if (processed) {
return res.status(200).json({ duplicate: true })
}
await processWebhook(payload)
await redis.setex(`webhook:${webhookId}`, 86400, '1')This ensures you only process each webhook once, even if NotiGrid retries.
Can I transform webhook payloads before processing?
Yes! You can transform data at three levels:
- In NotiGrid template - Use variables and Handlebars helpers
- In webhook endpoint - Transform after receiving
- Middleware layer - Use a transformation service between NotiGrid and your app
Example transformation:
function transformPayload(notigridPayload: any) {
return {
customerId: notigridPayload.data.variables.userId,
eventType: notigridPayload.event,
timestamp: new Date(notigridPayload.timestamp).toISOString()
}
}Best Practices Summary
Reliability
- ✅ Return 200 OK quickly (< 1 second)
- ✅ Process heavy operations asynchronously
- ✅ Implement idempotency using webhook IDs
- ✅ Use message queues for high-volume webhooks
- ✅ Set appropriate timeouts (10 seconds max)
Security
- ✅ Always verify HMAC signatures
- ✅ Use HTTPS endpoints only
- ✅ Validate timestamp to prevent replay attacks
- ✅ Implement rate limiting
- ✅ Validate payload schema
- ✅ Never log sensitive data (secrets, tokens)
Observability
- ✅ Log all webhook attempts with IDs
- ✅ Monitor success/failure rates
- ✅ Track processing time
- ✅ Alert on high failure rates (>5%)
- ✅ Store failed webhooks for debugging
Performance
- ✅ Use connection pooling for databases
- ✅ Cache frequently accessed data
- ✅ Batch process when possible
- ✅ Optimize database queries
- ✅ Use async processing for slow operations
Next Steps
Ready to implement webhooks in your notification infrastructure?
Get Started:
- Sign up for NotiGrid - Free tier available
- Create your first webhook integration (5 minutes)
- Set up your endpoint using the code examples above
- Test with webhook.site or ngrok
- Deploy and monitor
Learn More:
- Complete API Integration Guide - Full SDK setup guide
- Multi-Channel Notification System - Architecture patterns
- What Is Notification Infrastructure - Foundational concepts
- NotiGrid API Documentation - Full API reference
- Webhook Security Guide - Advanced security topics
Need Help?
Our team is here to help with webhook integration:
- 📧 Email: support@notigrid.com
- 📅 Book a demo for personalized guidance
- 📚 Browse documentation
Conclusion
Webhooks are the universal connector for modern notification infrastructure. They enable real-time, event-driven communication with any service—from custom CRMs to analytics platforms to internal tools.
By following the security best practices, implementing proper error handling, and using the patterns outlined in this guide, you can build robust webhook integrations that scale with your application.
Key Takeaways:
- ✅ Webhooks enable real-time notifications to any HTTP endpoint
- ✅ Always verify signatures and use HTTPS for security
- ✅ Return 200 OK quickly, process asynchronously
- ✅ Implement idempotency to handle retries safely
- ✅ Monitor webhook health and alert on failures
Whether you're integrating with Salesforce, Segment, Zapier, or your own microservices, webhooks give you the flexibility to send notifications anywhere your users need them.
Ready to get started? Create your first webhook integration →
Ready to send your first notification?
Get started with NotiGrid today and send notifications across email, SMS, Slack, and more.