vercel-webhooks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vercel Webhooks

Vercel Webhooks

When to Use This Skill

何时使用该技能

  • Setting up Vercel webhook handlers
  • Debugging signature verification failures
  • Understanding Vercel event types and payloads
  • Handling deployment, project, domain, or integration events
  • Monitoring deployment status changes
  • 搭建Vercel webhook处理器
  • 调试签名验证失败问题
  • 了解Vercel事件类型与负载
  • 处理部署、项目、域名或集成事件
  • 监控部署状态变更

Essential Code (USE THIS)

核心代码(请使用此代码)

Express Webhook Handler with Manual Verification

Express Webhook处理器(手动验证)

javascript
const express = require('express');
const crypto = require('crypto');

const app = express();

// CRITICAL: Use express.raw() for webhook endpoint - Vercel needs raw body
app.post('/webhooks/vercel',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const signature = req.headers['x-vercel-signature'];

    if (!signature) {
      return res.status(400).send('Missing x-vercel-signature header');
    }

    // Verify signature using SHA1 HMAC
    const expectedSignature = crypto
      .createHmac('sha1', process.env.VERCEL_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    // Use timing-safe comparison
    let signaturesMatch;
    try {
      signaturesMatch = crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );
    } catch (err) {
      // Buffer length mismatch = invalid signature
      signaturesMatch = false;
    }

    if (!signaturesMatch) {
      console.error('Invalid Vercel webhook signature');
      return res.status(400).send('Invalid signature');
    }

    // Parse the verified payload
    const event = JSON.parse(req.body.toString());

    // Handle the event
    switch (event.type) {
      case 'deployment.created':
        console.log('Deployment created:', event.payload.deployment.id);
        break;
      case 'deployment.succeeded':
        console.log('Deployment succeeded:', event.payload.deployment.id);
        break;
      case 'deployment.error':
        console.log('Deployment failed:', event.payload.deployment.id);
        break;
      case 'project.created':
        console.log('Project created:', event.payload.project.name);
        break;
      default:
        console.log('Unhandled event:', event.type);
    }

    res.json({ received: true });
  }
);
javascript
const express = require('express');
const crypto = require('crypto');

const app = express();

// CRITICAL: Use express.raw() for webhook endpoint - Vercel needs raw body
app.post('/webhooks/vercel',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const signature = req.headers['x-vercel-signature'];

    if (!signature) {
      return res.status(400).send('Missing x-vercel-signature header');
    }

    // Verify signature using SHA1 HMAC
    const expectedSignature = crypto
      .createHmac('sha1', process.env.VERCEL_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    // Use timing-safe comparison
    let signaturesMatch;
    try {
      signaturesMatch = crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );
    } catch (err) {
      // Buffer length mismatch = invalid signature
      signaturesMatch = false;
    }

    if (!signaturesMatch) {
      console.error('Invalid Vercel webhook signature');
      return res.status(400).send('Invalid signature');
    }

    // Parse the verified payload
    const event = JSON.parse(req.body.toString());

    // Handle the event
    switch (event.type) {
      case 'deployment.created':
        console.log('Deployment created:', event.payload.deployment.id);
        break;
      case 'deployment.succeeded':
        console.log('Deployment succeeded:', event.payload.deployment.id);
        break;
      case 'deployment.error':
        console.log('Deployment failed:', event.payload.deployment.id);
        break;
      case 'project.created':
        console.log('Project created:', event.payload.project.name);
        break;
      default:
        console.log('Unhandled event:', event.type);
    }

    res.json({ received: true });
  }
);

Python (FastAPI) Webhook Handler

Python(FastAPI)Webhook处理器

python
import os
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException, Header

app = FastAPI()
webhook_secret = os.environ.get("VERCEL_WEBHOOK_SECRET")

@app.post("/webhooks/vercel")
async def vercel_webhook(
    request: Request,
    x_vercel_signature: str = Header(None)
):
    if not x_vercel_signature:
        raise HTTPException(status_code=400, detail="Missing x-vercel-signature header")

    # Get raw body
    body = await request.body()

    # Compute expected signature
    expected_signature = hmac.new(
        webhook_secret.encode(),
        body,
        hashlib.sha1
    ).hexdigest()

    # Timing-safe comparison
    if not hmac.compare_digest(x_vercel_signature, expected_signature):
        raise HTTPException(status_code=400, detail="Invalid signature")

    # Parse verified payload
    event = await request.json()

    # Handle event
    if event["type"] == "deployment.created":
        print(f"Deployment created: {event['payload']['deployment']['id']}")
    elif event["type"] == "deployment.succeeded":
        print(f"Deployment succeeded: {event['payload']['deployment']['id']}")
    # ... handle other events

    return {"received": True}
For complete working examples with tests, see:
  • examples/express/ - Full Express implementation
  • examples/nextjs/ - Next.js App Router implementation
  • examples/fastapi/ - Python FastAPI implementation
python
import os
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException, Header

app = FastAPI()
webhook_secret = os.environ.get("VERCEL_WEBHOOK_SECRET")

@app.post("/webhooks/vercel")
async def vercel_webhook(
    request: Request,
    x_vercel_signature: str = Header(None)
):
    if not x_vercel_signature:
        raise HTTPException(status_code=400, detail="Missing x-vercel-signature header")

    # Get raw body
    body = await request.body()

    # Compute expected signature
    expected_signature = hmac.new(
        webhook_secret.encode(),
        body,
        hashlib.sha1
    ).hexdigest()

    # Timing-safe comparison
    if not hmac.compare_digest(x_vercel_signature, expected_signature):
        raise HTTPException(status_code=400, detail="Invalid signature")

    # Parse verified payload
    event = await request.json()

    # Handle event
    if event["type"] == "deployment.created":
        print(f"Deployment created: {event['payload']['deployment']['id']}")
    elif event["type"] == "deployment.succeeded":
        print(f"Deployment succeeded: {event['payload']['deployment']['id']}")
    # ... handle other events

    return {"received": True}
如需带测试的完整可运行示例,请查看:
  • examples/express/ - 完整Express实现
  • examples/nextjs/ - Next.js App Router实现
  • examples/fastapi/ - Python FastAPI实现

Common Event Types

常见事件类型

EventTriggered WhenCommon Use Cases
deployment.created
A new deployment startsStart deployment monitoring, notify team
deployment.succeeded
Deployment completes successfullyUpdate status, trigger post-deploy tasks
deployment.error
Deployment failsAlert team, rollback actions
deployment.canceled
Deployment is canceledClean up resources
project.created
New project is createdSet up monitoring, configure resources
project.removed
Project is deletedClean up external resources
domain.created
Domain is addedUpdate DNS, SSL configuration
See references/overview.md for the complete event list.
事件触发时机常见应用场景
deployment.created
新部署启动时启动部署监控、通知团队
deployment.succeeded
部署成功完成时更新状态、触发部署后任务
deployment.error
部署失败时通知团队、执行回滚操作
deployment.canceled
部署被取消时清理资源
project.created
新项目创建时配置监控、初始化资源
project.removed
项目被删除时清理外部关联资源
domain.created
域名添加时更新DNS、SSL配置
完整事件列表请查看references/overview.md

Environment Variables

环境变量

bash
undefined
bash
undefined

Required

Required

VERCEL_WEBHOOK_SECRET=your_webhook_secret_from_dashboard
VERCEL_WEBHOOK_SECRET=your_webhook_secret_from_dashboard

Optional (for API calls)

Optional (for API calls)

VERCEL_TOKEN=your_vercel_api_token
undefined
VERCEL_TOKEN=your_vercel_api_token
undefined

Local Development

本地开发

For local webhook testing, install Hookdeck CLI:
bash
undefined
如需进行本地webhook测试,请安装Hookdeck CLI:
bash
undefined

Install via npm

Install via npm

npm install -g hookdeck-cli
npm install -g hookdeck-cli

Or via Homebrew

Or via Homebrew

brew install hookdeck/hookdeck/hookdeck

Then start the tunnel:

```bash
hookdeck listen 3000 --path /webhooks/vercel
No account required. Provides local tunnel + web UI for inspecting requests.
brew install hookdeck/hookdeck/hookdeck

然后启动隧道:

```bash
hookdeck listen 3000 --path /webhooks/vercel
无需注册账号,提供本地隧道和用于查看请求的Web界面。

Reference Materials

参考资料

  • Webhook Overview - What Vercel webhooks are, all event types
  • Setup Guide - Configure webhooks in Vercel dashboard
  • Signature Verification - SHA1 HMAC verification details
  • Webhook Overview - Vercel webhook介绍、全量事件类型
  • Setup Guide - 在Vercel控制台配置webhook
  • Signature Verification - SHA1 HMAC验证细节

Recommended: webhook-handler-patterns

推荐技能:webhook-handler-patterns

For production-ready webhook handling, also install the
webhook-handler-patterns
skill to learn:
如需生产环境可用的webhook处理方案,还可安装
webhook-handler-patterns
技能来学习:

Related Skills

相关技能