Claude-code-plugins-plus vercel-webhooks-events
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/vercel-pack/skills/vercel-webhooks-events" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-vercel-webhooks-events && rm -rf "$T"
manifest:
plugins/saas-packs/vercel-pack/skills/vercel-webhooks-events/SKILL.mdsource content
Vercel Webhooks & Events
Overview
Handle Vercel webhook events (deployment.created, deployment.ready, deployment.error) with HMAC signature verification. Covers both integration webhooks (Vercel Marketplace) and project-level deploy hooks.
Prerequisites
- HTTPS endpoint accessible from the internet
- Webhook secret from Vercel dashboard or integration settings
module for HMAC signature verificationcrypto
Instructions
Step 1: Register a Webhook
In the Vercel dashboard:
- Go to Settings > Webhooks
- Add your endpoint URL (must be HTTPS)
- Select events to subscribe to
- Copy the webhook secret for signature verification
Or for Integration webhooks, configure in the Integration Console at
vercel.com/dashboard/integrations.
Step 2: Verify Webhook Signature
// api/webhooks/vercel.ts import type { VercelRequest, VercelResponse } from '@vercel/node'; import crypto from 'crypto'; const WEBHOOK_SECRET = process.env.VERCEL_WEBHOOK_SECRET!; function verifySignature(body: string, signature: string): boolean { const expectedSignature = crypto .createHmac('sha1', WEBHOOK_SECRET) .update(body) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } export default async function handler(req: VercelRequest, res: VercelResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } // Get raw body for signature verification const rawBody = JSON.stringify(req.body); const signature = req.headers['x-vercel-signature'] as string; if (!signature || !verifySignature(rawBody, signature)) { return res.status(401).json({ error: 'Invalid signature' }); } // Process the event const event = req.body; await handleEvent(event); res.status(200).json({ received: true }); }
Step 3: Handle Deployment Events
// lib/webhook-handlers.ts interface VercelWebhookEvent { id: string; type: string; createdAt: number; payload: { deployment: { id: string; name: string; url: string; meta: Record<string, string>; }; project: { id: string; name: string; }; target: 'production' | 'preview' | null; user: { id: string; email: string; username: string }; }; } async function handleEvent(event: VercelWebhookEvent): Promise<void> { switch (event.type) { case 'deployment.created': console.log(`Deployment started: ${event.payload.deployment.url}`); // Notify Slack, update status board, etc. break; case 'deployment.ready': console.log(`Deployment ready: ${event.payload.deployment.url}`); // Run smoke tests against the deployment URL // Notify team of successful deploy if (event.payload.target === 'production') { await notifyProductionDeploy(event); } break; case 'deployment.error': console.error(`Deployment failed: ${event.payload.deployment.id}`); // Alert on-call engineer // Create incident ticket await notifyDeploymentError(event); break; case 'deployment.canceled': console.log(`Deployment canceled: ${event.payload.deployment.id}`); break; case 'project.created': console.log(`New project: ${event.payload.project.name}`); break; case 'project.removed': console.log(`Project removed: ${event.payload.project.name}`); break; default: console.log(`Unhandled event type: ${event.type}`); } }
Step 4: Idempotency — Prevent Duplicate Processing
// lib/idempotency.ts // Vercel may retry webhook delivery — track processed event IDs const processedEvents = new Set<string>(); // Use Redis in production async function processWebhookIdempotent( event: VercelWebhookEvent, handler: (e: VercelWebhookEvent) => Promise<void> ): Promise<boolean> { if (processedEvents.has(event.id)) { console.log(`Skipping duplicate event: ${event.id}`); return false; } await handler(event); processedEvents.add(event.id); return true; }
Step 5: Slack Notification Example
// lib/notifications.ts async function notifyProductionDeploy(event: VercelWebhookEvent): Promise<void> { const { deployment, project, user } = event.payload; await fetch(process.env.SLACK_WEBHOOK_URL!, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: `Production deploy complete`, blocks: [ { type: 'section', text: { type: 'mrkdwn', text: [ `*${project.name}* deployed to production`, `By: ${user.username}`, `URL: https://${deployment.url}`, `Commit: ${deployment.meta?.githubCommitMessage ?? 'N/A'}`, ].join('\n'), }, }, ], }), }); } async function notifyDeploymentError(event: VercelWebhookEvent): Promise<void> { await fetch(process.env.SLACK_WEBHOOK_URL!, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: `Deployment FAILED for ${event.payload.project.name} — ${event.payload.deployment.id}`, }), }); }
Step 6: Test Webhooks Locally
# Use the Vercel CLI to test webhook signatures # Or use a tunnel service for local testing npx localtunnel --port 3000 # Gives you a public URL like https://xxx.loca.lt # Send a test webhook payload curl -X POST http://localhost:3000/api/webhooks/vercel \ -H "Content-Type: application/json" \ -H "x-vercel-signature: $(echo -n '{"type":"deployment.ready","id":"test"}' | openssl dgst -sha1 -hmac 'your-secret' | awk '{print $2}')" \ -d '{"type":"deployment.ready","id":"test"}'
Webhook Event Types
| Event | Trigger |
|---|---|
| New deployment started building |
| Deployment build completed successfully |
| Deployment build failed |
| Deployment was canceled |
| New project created |
| Project deleted |
| Domain added to project |
| Integration uninstalled |
Output
- Webhook endpoint with HMAC signature verification
- Event handlers for deployment lifecycle events
- Idempotent processing preventing duplicates
- Slack notifications for production deploys and failures
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Wrong webhook secret or body mismatch | Verify secret matches dashboard, use raw body for HMAC |
| Webhook not received | Endpoint not publicly accessible | Ensure HTTPS, check firewall rules |
| Duplicate processing | Webhook retried by Vercel | Implement idempotency with event ID tracking |
on webhook endpoint | Handler takes too long | Return 200 immediately, process async in background |
Missing | Not a real Vercel webhook | Reject requests without the signature header |
Resources
Next Steps
For performance optimization, see
vercel-performance-tuning.