Request Forwarding
The CLI's forwarding feature allows you to send webhook events to your local development environment in real-time. This is essential for testing webhook integrations during development.
How Forwarding Works
When forwarding is enabled, the CLI acts as a proxy:
- Webhook arrives at your endpoint
- CLI receives the event via WebSocket
- CLI forwards the request to your local server
- Your handler processes the webhook
- Response flows back through the CLI
Webhook Source → Your Endpoint → CLI → Local Server
↓
Response Status
Basic Forwarding
Start Forwarding
Forward all webhook events to your local development server:
hookvm listen https://hookvm.com/hooks/your-endpoint-id \
--target http://localhost:3000/webhooks
Short form using -t:
hookvm listen endpoint-url -t http://localhost:3000/webhooks
Target URL Format
The target URL should point to your local webhook handler:
# Different local development setups
-t http://localhost:3000/webhooks # Express.js
-t http://localhost:8000/api/webhooks # Django
-t http://localhost:3000/api/webhooks # Next.js
-t http://localhost:4567/webhook-handler # Sinatra
-t https://localhost:3443/webhooks # HTTPS local
Forwarding Indicators
Visual Status
In the interactive UI, you'll see forwarding status:
🟢 POST / [12:34:56] → ✅ 200
├── 📥 Received (192.168.1.100)
└── 📤 Forwarded → localhost:3000 (✅ 200 OK)
Status Icons
- 📤 - Forwarding in progress or completed
- ✅ - Successful forwarding (2xx response)
- ❌ - Failed forwarding (error or non-2xx response)
- ⏳ - Forwarding in progress
Response Codes
The CLI shows the response from your local handler:
- ✅ 200 OK - Handler processed successfully
- ❌ 404 Not Found - Handler route doesn't exist
- ❌ 500 Internal Server Error - Handler had an error
- ❌ Connection refused - Local server not running
Advanced Forwarding
Multiple Endpoints
Forward different endpoints to different local handlers:
# Terminal 1 - Payment webhooks
hookvm listen https://hookvm.com/hooks/payment-endpoint \
--target http://localhost:3000/webhooks/payments
# Terminal 2 - User webhooks
hookvm listen https://hookvm.com/hooks/user-endpoint \
--target http://localhost:3000/webhooks/users
# Terminal 3 - Order webhooks
hookvm listen https://hookvm.com/hooks/order-endpoint \
--target http://localhost:3000/webhooks/orders
Custom Headers
Add headers to forwarded requests:
hookvm listen endpoint-url \
--target http://localhost:3000/webhooks \
--header "Authorization: Bearer dev-token-123" \
--header "X-Environment: development" \
--header "X-Source: webhook-cli"
HTTPS Local Servers
Forward to HTTPS local development servers:
hookvm listen endpoint-url \
--target https://localhost:3443/webhooks
Note: You may need to configure your local server with SSL certificates.
Request Details
What Gets Forwarded
The CLI forwards the complete webhook request:
HTTP Method
- Always
POST(webhooks are POST requests)
Headers
Essential headers are forwarded:
Content-TypeUser-AgentAuthorization(if present)- Custom headers from the webhook source
- Any headers added with
--header
Body
- Complete request body (JSON, form data, etc.)
- Preserved exactly as received
Query Parameters
- Any query parameters from the original request
What's Added
The CLI adds these headers to forwarded requests:
X-Forwarded-By: webhook-cli
X-Original-Host: hookvm.com
X-Forwarded-For: [original-ip]
Local Handler Setup
Basic Handler
Your local server needs to handle POST requests:
Node.js/Express
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks', (req, res) => {
console.log('Webhook received:', {
headers: req.headers,
body: req.body
});
// Process the webhook
// Always respond with success
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Python/Flask
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
print(f"Webhook received: {request.get_json()}")
# Process the webhook
return jsonify({"received": True}), 200
if __name__ == '__main__':
app.run(port=3000, debug=True)
Next.js API Route
// pages/api/webhooks.js
export default function handler(req, res) {
if (req.method === 'POST') {
console.log('Webhook received:', req.body);
// Process the webhook
res.status(200).json({ received: true });
} else {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Debugging Forwarded Requests
Response Time Monitoring
The CLI shows how long your handler takes to respond:
🟢 POST / [12:34:56] → ✅ 200 (142ms)
├── 📥 Received (webhook-source)
└── 📤 Forwarded → localhost:3000 (✅ 200 OK, 142ms)
Error Handling
When your handler returns errors:
🔴 POST / [12:34:56] → ❌ 500 Internal Server Error
├── 📥 Received (webhook-source)
└── 📤 Forwarded → localhost:3000 (❌ 500 Internal Server Error)
Connection Issues
When your local server isn't available:
🔴 POST / [12:34:56] → ❌ Connection refused
├── 📥 Received (webhook-source)
└── 📤 Forwarded → localhost:3000 (❌ Connection refused)
Testing Your Handler
Manual Testing
Test your handler directly:
# Test with curl
curl -X POST http://localhost:3000/webhooks \
-H "Content-Type: application/json" \
-d '{
"event": "test.event",
"data": {
"message": "Hello from manual test"
}
}'
Webhook Test Events
Use your webhook source's test feature:
- Stripe: Dashboard → Webhooks → Send test webhook
- GitHub: Settings → Webhooks → Redeliver
- Shopify: Settings → Notifications → Test webhook
CLI Test Mode
Send test events through the CLI:
# Send a test event to your endpoint
curl -X POST https://hookvm.com/hooks/your-endpoint-id \
-H "Content-Type: application/json" \
-d '{
"test": true,
"message": "Test from CLI"
}'
Performance Considerations
Handler Response Time
Webhook sources expect quick responses:
- < 5 seconds: Excellent
- < 10 seconds: Good
- < 30 seconds: Acceptable
- > 30 seconds: May cause timeouts
Async Processing
For heavy processing, use async patterns:
app.post('/webhooks', async (req, res) => {
// Respond immediately
res.status(200).json({ received: true });
// Process asynchronously
setTimeout(() => {
processWebhookData(req.body);
}, 0);
});
Error Recovery
Handle errors gracefully:
app.post('/webhooks', (req, res) => {
try {
processWebhook(req.body);
res.status(200).json({ success: true });
} catch (error) {
console.error('Webhook processing error:', error);
// Still return 200 to avoid retries for permanent errors
res.status(200).json({
success: false,
error: 'Processing failed'
});
}
});
Security Best Practices
Verify Forwarded Requests
Add verification to your handler:
app.post('/webhooks', (req, res) => {
// Check if request came from webhook-cli
if (req.headers['x-forwarded-by'] !== 'webhook-cli') {
return res.status(401).json({ error: 'Invalid source' });
}
// Process the webhook
});
Local Development Only
Never use forwarding in production:
if (process.env.NODE_ENV === 'production') {
// Direct webhook handling in production
} else {
// Accept forwarded requests in development
}
Troubleshooting Forwarding
Common Issues
"Connection refused"
- Check: Local server is running
- Check: Port number is correct
- Check: No firewall blocking connections
"404 Not Found"
- Check: Handler route exists (
/webhooks) - Check: Route accepts POST requests
- Check: No typos in the path
"Handler not called"
- Check: Handler is properly configured
- Check: No middleware blocking requests
- Check: Application logs for errors
Debug Commands
# Check if local server is running
curl http://localhost:3000/webhooks
# Check specific handler
curl -X POST http://localhost:3000/webhooks \
-H "Content-Type: application/json" \
-d '{"test": true}'
# Check what's listening on port
lsof -i :3000
Ready for production? Learn about webhook security and deployment best practices.