Testing Webhooks
Testing webhooks can be challenging since they require a publicly accessible HTTPS endpoint. This guide covers strategies for testing webhooks during development and debugging production issues.
Using the Test Endpoint
The easiest way to test your webhook integration is using the built-in test endpoint.
Send a Test Webhook
curl -X POST https://api.prod.trstinc.ca/v1/project/{project_id}/webhooks/{webhook_id}/test \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"delivery_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"webhook_id": "webhook_abc123",
"event_type": "webhook.test",
"created_at": "2024-11-02T15:30:00Z"
}This sends a webhook.test event to your configured endpoint:
{
"event_id": "test_550e8400",
"event_type": "webhook.test",
"timestamp": "2024-11-02T15:30:00Z",
"project_id": "proj_abc123",
"data": {
"test_id": "test_550e8400",
"message": "This is a test webhook",
"triggered_at": "2024-11-02T15:30:00Z"
}
}Verify Test Delivery
Check if the test webhook was delivered successfully:
curl https://api.prod.trstinc.ca/v1/project/{project_id}/webhooks/{webhook_id}/deliveries \
-H "Authorization: Bearer YOUR_API_KEY"Look for the delivery with the matching delivery_id from the test response.
Local Development
Option 1: Using ngrok (Recommended)
ngrok creates a secure tunnel to your localhost:
Install ngrok:
bashbrew install ngrok # macOS # or download from ngrok.comStart your local server:
bashnode server.js # Listening on port 3000Create ngrok tunnel:
bashngrok http 3000You'll see output like:
Forwarding: https://abc123.ngrok.io -> http://localhost:3000Use the ngrok URL for your webhook:
bashcurl -X POST https://api.prod.trstinc.ca/v1/project/{project_id}/webhooks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://abc123.ngrok.io/webhooks/trst", "event_types": ["enrollment.completed"], "enabled": true }'Monitor requests in ngrok dashboard:
- Open
http://localhost:4040in your browser to see all requests - Inspect headers, payloads, and responses
- Replay requests for testing
- Open
Option 2: Using Cloudflare Tunnel
Cloudflare Tunnel is a free alternative to ngrok:
Install cloudflared:
bashbrew install cloudflare/cloudflare/cloudflaredStart tunnel:
bashcloudflared tunnel --url http://localhost:3000Use the provided URL for your webhook
Option 3: Using LocalTunnel
LocalTunnel is another simple option:
Install:
bashnpm install -g localtunnelStart tunnel:
bashlt --port 3000Use the provided URL
Debugging Failed Deliveries
View Delivery History
View delivery histroy in the web interface or get a list of all webhook deliveries:
curl "https://api.prod.trstinc.ca/v1/project/{project_id}/webhooks/{webhook_id}/deliveries?from=2024-11-01T00:00:00Z&to=2024-11-02T23:59:59Z" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"subscription_id": "webhook_abc123",
"payload": {
"event_id": "event_xyz789",
"timestamp": "2024-11-02T15:30:00Z",
"project_id": "proj_abc123",
"data": {
"event_type": "enrollment.completed",
"session_id": "session_123",
"member_id": "member_456",
"device_id": "device_789",
"completed_at": "2024-11-02T15:30:00Z"
}
},
"attempt_count": 1,
"attempt_pending": false,
"status": "success",
"response_status": 200,
"response_body": "OK",
"created_at": "2024-11-02T15:30:00Z",
"last_attempt_at": "2024-11-02T15:30:01Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"subscription_id": "webhook_abc123",
"payload": {
"event_id": "event_uvw456",
"timestamp": "2024-11-02T14:00:00Z",
"project_id": "proj_abc123",
"data": {
"event_type": "demo.scan.success",
"member_id": "member_789",
"device_id": "device_123",
"scanned_at": "2024-11-02T14:00:00Z"
}
},
"attempt_count": 3,
"attempt_pending": true,
"status": "failed",
"response_status": 500,
"response_body": "Internal Server Error",
"created_at": "2024-11-02T14:00:00Z",
"last_attempt_at": "2024-11-02T14:05:00Z"
}
],
"total_count": 2
}View Delivery Attempt Details
For failed deliveries, view detailed attempt information:
curl "https://api.prod.trstinc.ca/v1/project/{project_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/attempts" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"attempts": [
{
"attempt_number": 1,
"attempted_at": "2024-11-02T14:00:00Z",
"response_status": 500,
"response_body": "Internal Server Error",
"duration_ms": 1523,
"error": "HTTP 500 - Internal Server Error"
},
{
"attempt_number": 2,
"attempted_at": "2024-11-02T14:00:30Z",
"response_status": 500,
"response_body": "Internal Server Error",
"duration_ms": 1842,
"error": "HTTP 500 - Internal Server Error"
},
{
"attempt_number": 3,
"attempted_at": "2024-11-02T14:01:30Z",
"response_status": null,
"duration_ms": 20000,
"error": "Request timeout after 20000ms"
}
]
}Common Failure Scenarios
1. Signature Verification Failed
Symptom: Deliveries show 401 Unauthorized
Possible causes:
- Using wrong webhook secret
- Not using raw request body
- Incorrect signature comparison
Debug steps:
- Log the received signature and computed signature
- Verify you're using the correct secret
- Check that you're using the raw body (not parsed JSON)
- See Security Guide for correct implementation
2. Request Timeout
Symptom: Deliveries show "Request timeout"
Possible causes:
- Endpoint takes >20 seconds to respond
- Synchronous processing of events
- Slow database queries or external API calls
Solution:
app.post("/webhooks/trst", async (req, res) => {
// Verify signature
verifySignature(req)
// Acknowledge IMMEDIATELY
res.status(200).send("OK")
// Process asynchronously (don't await!)
processWebhook(req.body).catch(console.error)
})3. Connection Refused
Symptom: Deliveries show "Connection refused"
Possible causes:
- Server is down
- Firewall blocking requests
- Wrong URL/port
Debug steps:
- Verify your server is running
- Check that the webhook URL is correct
- Test with curl from outside your network
- Check firewall rules
4. SSL Certificate Errors
Symptom: Deliveries show SSL/TLS errors
Possible causes:
- Self-signed certificate
- Expired certificate
- Certificate chain issues
Solution:
- Use a valid SSL certificate (Let's Encrypt is free)
- Ensure certificate chain is complete
- Test with:
curl -v https://your-endpoint.com
Testing Checklist
Before Going Live
[ ] Signature verification implemented and tested
- [ ] Using raw request body
- [ ] Correct secret from webhook creation
- [ ] Constant-time comparison
[ ] Endpoint responds within 20 seconds
- [ ] Immediate acknowledgment (200 OK)
- [ ] Asynchronous event processing
[ ] Idempotency handling
- [ ] Check
event_idbefore processing - [ ] Skip duplicate events gracefully
- [ ] Check
[ ] Error handling
- [ ] Catch and log all errors
- [ ] Return appropriate HTTP status codes
- [ ] Don't expose internal errors in response
[ ] Monitoring
- [ ] Log all webhook receipts
- [ ] Alert on repeated failures
- [ ] Track processing time
[ ] Test all event types
- [ ] enrollment.completed
- [ ] demo.scan.success
- [ ] webhook.test
Testing Script
// test-webhook.js
const crypto = require('crypto');
function generateTestWebhook(secret) {
const payload = {
event_id: "test_" + Date.now(),
event_type: "webhook.test",
timestamp: new Date().toISO String(),
project_id: "proj_test",
data: {
test_id: "test_" + Date.now(),
message: "Test webhook",
triggered_at: new Date().toISOString()
}
};
const body = JSON.stringify(payload);
// Generate signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(body);
const signature = 'sha256=' + hmac.digest('hex');
return { body, signature };
}
// Usage
const { body, signature } = generateTestWebhook('your_webhook_secret');
console.log('POST to your endpoint with:');
console.log('Header: X-Webhook-Signature:', signature);
console.log('Body:', body);Monitoring Production Webhooks
Set Up Alerts
Monitor for webhook issues:
// Example: Alert on 3 consecutive failures
async function checkWebhookHealth(webhookId) {
const deliveries = await fetch(
`https://api.prod.trstinc.ca/v1/project/${projectId}/webhooks/${webhookId}/deliveries?per_page=10`,
{
headers: { Authorization: `Bearer ${apiKey}` },
},
).then((r) => r.json())
const recentFailures = deliveries.deliveries.slice(0, 3).filter((d) => d.status === "failed")
if (recentFailures.length === 3) {
// Alert team
await sendAlert({
severity: "high",
message: `Webhook ${webhookId} has 3 consecutive failures`,
details: recentFailures,
})
}
}Dashboard Metrics
Track key metrics:
- Delivery success rate (last 24h, 7d, 30d)
- Average delivery latency
- Failed delivery count
- Events by type
Log Structured Data
// Log webhook events in structured format
function logWebhookEvent(event, metadata) {
logger.info({
type: "webhook_received",
event_id: event.event_id,
event_type: event.event_type,
project_id: event.project_id,
delivery_id: metadata.deliveryId,
processing_time_ms: metadata.processingTime,
timestamp: new Date().toISOString(),
})
}Troubleshooting Tools
1. Webhook.site
Webhook.site provides a temporary URL to inspect webhooks:
- Go to webhook.site
- Copy the unique URL
- Create a webhook subscription with that URL
- Trigger events and inspect the requests
2. RequestBin
RequestBin is similar to Webhook.site:
- Create a request bin
- Use the bin URL for your webhook
- View all requests in real-time
3. Ngrok Inspector
ngrok includes a web interface at http://localhost:4040:
- See all requests/responses
- Replay requests
- Inspect headers and bodies
4. Server Logs
Always log comprehensive webhook data:
app.post("/webhooks/trst", (req, res) => {
const event = JSON.parse(req.body.toString())
logger.info("Webhook received", {
eventId: event.event_id,
eventType: event.data.event_type,
timestamp: new Date().toISOString(),
ip: req.ip,
})
// ... verification and processing
})Next Steps
- Best Practices - Production-ready patterns
- Security Guide - Implement signature verification
- Event Reference - Webhook payload schemas
- API Reference - Webhook management endpoints