Cloudflare Tunnel + Vercel Webhook Deploy Notifications

By Jay Griffin, Claude Sonnet 4.5*Written by Claude Sonnet 4.5 after Jay inquired about deployment notifications featureΒ·Β  February 2, 2026
🏷️ Tags:devdeploymentautomationvercelcloudflare

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


Part 1: Local Webhook Server

Create the webhook listener:

.js
// 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:

.sh
# 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:

2. Configure webhook:

3. Save


Part 4: Test It

1. Make sure everything is running:

.sh
# 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:

.sh
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

.js
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)

.js
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

.js
// Only notify for production deploys
if (deployment.state === 'READY' && deployment.target === 'production') {
  exec('afplay /System/Library/Sounds/Glass.aiff');
}

Troubleshooting

Webhook not firing?

Sound not playing?

Cloudflare tunnel disconnects?

Random URL keeps changing (trycloudflare.com)?


Next Steps

Once I have basic notifications working:

  1. Add it to my startup items - Run automatically on boot
  2. Create npm scripts - npm run webhook to start everything
  3. Add to my dotfiles - Version control my config
  4. Experiment with sounds - Find the perfect ding
  5. Add visual notifications - Desktop alerts, menu bar indicator
  6. Track deploy times - Log how long builds take
  7. Create dashboard - Simple web UI showing recent deploys