Cloudflare Tunnel + Vercel Webhook Deploy Notifications
My master plan to get a satisfying ding sound on my Mac every time a Vercel deployment goes live. Time to stop refresh-spamming and get notified automatically.
What I'm Building
git push β GitHub β Vercel builds β Vercel webhook β Cloudflare Tunnel β Local server β π DING
Prerequisites
- Cloudflare account (free)
- Domain name (optional but recommended for permanent setup)
- Node.js installed locally
- Vercel project already deploying
Part 1: Local Webhook Server
Create the webhook listener:
// webhook-listener.js
const express = require('express');
const { exec } = require('child_process');
const app = express();
app.use(express.json());
app.post('/vercel-webhook', (req, res) => {
const { type, deployment } = req.body;
console.log(`[${new Date().toLocaleTimeString()}] ${type}: ${deployment.state}`);
// Play sound when deployment is ready
if (deployment.state === 'READY') {
exec('afplay /System/Library/Sounds/Glass.aiff');
console.log('π DEPLOYMENT LIVE:', deployment.url);
}
// Optional: Different sound for errors
if (deployment.state === 'ERROR') {
exec('afplay /System/Library/Sounds/Basso.aiff');
console.error('β DEPLOYMENT FAILED');
}
res.status(200).json({ received: true });
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
const PORT = 3001;
app.listen(PORT, () => {
console.log(`π§ Webhook listener running on http://localhost:${PORT}`);
console.log(`Health check: http://localhost:${PORT}/health`);
});
Install dependencies: npm init -y then npm install express
Test locally: node webhook-listener.js
Part 2: Cloudflare Tunnel Setup
Option A: Quick Test (Temporary URL)
Fastest way to test:
# Install cloudflared
brew install cloudflare/cloudflare/cloudflared
# Login to Cloudflare
cloudflared tunnel login
# Run temporary tunnel (URL changes on restart)
cloudflared tunnel --url http://localhost:3001
I'll get a URL like https://random-words.trycloudflare.com
I can use this to test the webhook before committing to permanent setup.
Option B: Permanent Setup (Recommended)
1. Install cloudflared: brew install cloudflare/cloudflare/cloudflared
2. Login: cloudflared tunnel login
This opens browser for authorization.
3. Create named tunnel: cloudflared tunnel create vercel-webhook
Outputs tunnel ID like abc123-def456-ghi789
4. Create config file:
# ~/.cloudflared/config.yml
tunnel: <my-tunnel-id>
credentials-file: /Users/<my-username>/.cloudflared/<tunnel-id>.json
ingress:
- hostname: webhook.mydomain.com # Or webhook.jaygriff.com
service: http://localhost:3001
- service: http_status:404
5. Setup DNS (if using my own domain): cloudflared tunnel route dns vercel-webhook webhook.mydomain.com
6. Run tunnel: cloudflared tunnel run vercel-webhook
7. Make it run on startup (optional but recommended): cloudflared service install then sudo launchctl load /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
Part 3: Configure Vercel Webhook
1. Go to my Vercel project:
- Settings β Webhooks β Add Webhook
2. Configure webhook:
- URL:
https://webhook.mydomain.com/vercel-webhook(or my trycloudflare.com URL) - Events: Select:
- β Deployment Created
- β Deployment Ready
- β Deployment Error (optional)
3. Save
Part 4: Test It
1. Make sure everything is running:
# Terminal 1: Webhook listener
node webhook-listener.js
# Terminal 2: Cloudflare tunnel (if not running as service)
cloudflared tunnel run vercel-webhook
2. Deploy something:
git add .
git commit -m "test: trigger deploy webhook"
git push
3. Watch my terminal and listen for the sound!
Customization Ideas
Different Sounds for Different Events
const sounds = {
BUILDING: '/System/Library/Sounds/Tink.aiff',
READY: '/System/Library/Sounds/Glass.aiff',
ERROR: '/System/Library/Sounds/Basso.aiff',
};
if (deployment.state in sounds) {
exec(`afplay ${sounds[deployment.state]}`);
}
Desktop Notification (macOS)
function notify(title, message) {
exec(`osascript -e 'display notification "${message}" with title "${title}"'`);
}
if (deployment.state === 'READY') {
exec('afplay /System/Library/Sounds/Glass.aiff');
notify('π Deployment Ready', deployment.url);
}
Filter by Environment
// Only notify for production deploys
if (deployment.state === 'READY' && deployment.target === 'production') {
exec('afplay /System/Library/Sounds/Glass.aiff');
}
Troubleshooting
Webhook not firing?
- Check Vercel webhook logs (Settings β Webhooks β Click my webhook)
- Verify tunnel is running:
curl https://webhook.mydomain.com/health - Check local server is listening:
curl http://localhost:3001/health
Sound not playing?
- Test sound directly:
afplay /System/Library/Sounds/Glass.aiff - Check terminal output for errors
- Verify webhook body contains expected fields
Cloudflare tunnel disconnects?
- Run as service (see Part 2, step 7)
- Check tunnel status:
cloudflared tunnel info vercel-webhook
Random URL keeps changing (trycloudflare.com)?
- I'm using temporary tunnel mode
- Follow "Option B: Permanent Setup" for static URL
Next Steps
Once I have basic notifications working:
- Add it to my startup items - Run automatically on boot
- Create npm scripts -
npm run webhookto start everything - Add to my dotfiles - Version control my config
- Experiment with sounds - Find the perfect ding
- Add visual notifications - Desktop alerts, menu bar indicator
- Track deploy times - Log how long builds take
- Create dashboard - Simple web UI showing recent deploys