Vercel Integration
Receive and route Vercel webhooks for deployments, domains, projects, and other platform events.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Vercel Production",
"slug": "vercel",
"verificationConfig": {
"type": "hmac",
"secret": "your-vercel-webhook-secret",
"algorithm": "sha1",
"header": "x-vercel-signature",
"encoding": "hex"
}
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/vercel2. Configure Vercel Webhook
- Go to Vercel Dashboard and select your team
- Navigate to Settings → Webhooks
- Click Create Webhook
- Enter your Hookbase URL as the Endpoint URL
- Enter a Secret — this must match the
secretin your Hookbase source - Select the events you want to receive (e.g.,
deployment.created,deployment.succeeded) - Click Create Webhook
3. Create Routes
# Create destination for your deployment handler
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Deploy Service", "url": "https://api.myapp.com/webhooks/vercel"}'
# Create route
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Vercel to Deploy Service", "sourceId": "src_...", "destinationIds": ["dst_..."]}'Signature Verification
Vercel signs webhooks using HMAC-SHA1 with the secret you configure when creating the webhook endpoint. The signature is sent in the x-vercel-signature header as a hex-encoded digest.
x-vercel-signature: a3f7b9c2e1d4f6a8b0c3e5d7f9a1b3c5d7e9f0a2Hookbase automatically verifies this when configured:
{
"verificationConfig": {
"type": "hmac",
"secret": "your-vercel-webhook-secret",
"algorithm": "sha1",
"header": "x-vercel-signature",
"encoding": "hex"
}
}How Verification Works
- Hookbase receives the raw request body
- Computes
HMAC-SHA1(secret, rawBody)and hex-encodes the result - Compares the computed value against the
x-vercel-signatureheader - Rejects the webhook if the signatures do not match
Common Events
deployment.created
Triggered when a new deployment is initiated.
{
"id": "evt_abc123def456",
"type": "deployment.created",
"createdAt": 1709827200000,
"payload": {
"deployment": {
"id": "dpl_7Hx9qPzR4mN2vL5w",
"url": "my-app-abc123-team.vercel.app",
"name": "my-app",
"meta": {
"githubCommitSha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
"githubCommitMessage": "feat: add user dashboard",
"githubCommitAuthorName": "Jane Smith",
"githubCommitRef": "main",
"githubOrg": "my-org",
"githubRepo": "my-app"
},
"projectId": "prj_9kL3mN5pQ7rS1tU3",
"target": "production",
"inspectorUrl": "https://vercel.com/my-team/my-app/dpl_7Hx9qPzR4mN2vL5w"
},
"project": {
"id": "prj_9kL3mN5pQ7rS1tU3",
"name": "my-app"
},
"team": {
"id": "team_4vW6xY8zA0bC2dE4",
"slug": "my-team"
},
"user": {
"id": "usr_fG6hI8jK0lM2nO4p"
}
}
}deployment.succeeded
Triggered when a deployment completes successfully.
{
"id": "evt_ghi789jkl012",
"type": "deployment.succeeded",
"createdAt": 1709827500000,
"payload": {
"deployment": {
"id": "dpl_7Hx9qPzR4mN2vL5w",
"url": "my-app-abc123-team.vercel.app",
"name": "my-app",
"meta": {
"githubCommitSha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
"githubCommitMessage": "feat: add user dashboard",
"githubCommitAuthorName": "Jane Smith",
"githubCommitRef": "main",
"githubOrg": "my-org",
"githubRepo": "my-app"
},
"projectId": "prj_9kL3mN5pQ7rS1tU3",
"target": "production",
"inspectorUrl": "https://vercel.com/my-team/my-app/dpl_7Hx9qPzR4mN2vL5w",
"readyState": "READY"
},
"project": {
"id": "prj_9kL3mN5pQ7rS1tU3",
"name": "my-app"
},
"team": {
"id": "team_4vW6xY8zA0bC2dE4",
"slug": "my-team"
}
}
}deployment.error
Triggered when a deployment fails.
{
"id": "evt_mno345pqr678",
"type": "deployment.error",
"createdAt": 1709827800000,
"payload": {
"deployment": {
"id": "dpl_3bD5eF7gH9iJ1kL3",
"url": "my-app-def456-team.vercel.app",
"name": "my-app",
"meta": {
"githubCommitSha": "b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1",
"githubCommitMessage": "fix: resolve build error",
"githubCommitAuthorName": "John Doe",
"githubCommitRef": "feature/auth",
"githubOrg": "my-org",
"githubRepo": "my-app"
},
"projectId": "prj_9kL3mN5pQ7rS1tU3",
"target": "preview",
"readyState": "ERROR",
"errorMessage": "Command \"npm run build\" exited with 1"
},
"project": {
"id": "prj_9kL3mN5pQ7rS1tU3",
"name": "my-app"
},
"team": {
"id": "team_4vW6xY8zA0bC2dE4",
"slug": "my-team"
}
}
}deployment.canceled
Triggered when a deployment is canceled.
{
"id": "evt_stu901vwx234",
"type": "deployment.canceled",
"createdAt": 1709828100000,
"payload": {
"deployment": {
"id": "dpl_5nM7oP9qR1sT3uV5",
"url": "my-app-ghi789-team.vercel.app",
"name": "my-app",
"meta": {
"githubCommitSha": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2",
"githubCommitMessage": "chore: update dependencies",
"githubCommitAuthorName": "Jane Smith",
"githubCommitRef": "main",
"githubOrg": "my-org",
"githubRepo": "my-app"
},
"projectId": "prj_9kL3mN5pQ7rS1tU3",
"target": "production",
"readyState": "CANCELED"
},
"project": {
"id": "prj_9kL3mN5pQ7rS1tU3",
"name": "my-app"
}
}
}domain.created
Triggered when a new domain is added to a project.
{
"id": "evt_yza567bcd890",
"type": "domain.created",
"createdAt": 1709828400000,
"payload": {
"domain": {
"name": "app.example.com",
"projectId": "prj_9kL3mN5pQ7rS1tU3",
"redirect": null,
"configuredBy": "CNAME",
"verified": true
},
"project": {
"id": "prj_9kL3mN5pQ7rS1tU3",
"name": "my-app"
},
"team": {
"id": "team_4vW6xY8zA0bC2dE4",
"slug": "my-team"
}
}
}project.created
Triggered when a new project is created.
{
"id": "evt_efg123hij456",
"type": "project.created",
"createdAt": 1709828700000,
"payload": {
"project": {
"id": "prj_2wX4yZ6aB8cD0eF2",
"name": "new-service",
"framework": "nextjs",
"gitRepository": {
"type": "github",
"repo": "my-org/new-service"
}
},
"team": {
"id": "team_4vW6xY8zA0bC2dE4",
"slug": "my-team"
},
"user": {
"id": "usr_fG6hI8jK0lM2nO4p"
}
}
}project.removed
Triggered when a project is deleted.
{
"id": "evt_klm789nop012",
"type": "project.removed",
"createdAt": 1709829000000,
"payload": {
"project": {
"id": "prj_2wX4yZ6aB8cD0eF2",
"name": "old-service"
},
"team": {
"id": "team_4vW6xY8zA0bC2dE4",
"slug": "my-team"
},
"user": {
"id": "usr_fG6hI8jK0lM2nO4p"
}
}
}Transform Examples
Slack Alert for Deployment Status
function transform(payload) {
const deployment = payload.payload.deployment;
const project = payload.payload.project;
const meta = deployment.meta || {};
const eventType = payload.type;
const statusMap = {
"deployment.created": { emoji: ":rocket:", color: "#3498db", text: "Deployment Started" },
"deployment.succeeded": { emoji: ":white_check_mark:", color: "#2ecc71", text: "Deployment Succeeded" },
"deployment.error": { emoji: ":x:", color: "#e74c3c", text: "Deployment Failed" },
"deployment.canceled": { emoji: ":no_entry_sign:", color: "#95a5a6", text: "Deployment Canceled" }
};
const status = statusMap[eventType] || { emoji: ":bell:", color: "#7f8c8d", text: eventType };
return {
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: `${status.emoji} ${status.text}`
}
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Project:*\n${project.name}`
},
{
type: "mrkdwn",
text: `*Target:*\n${deployment.target || "preview"}`
},
{
type: "mrkdwn",
text: `*Branch:*\n${meta.githubCommitRef || "N/A"}`
},
{
type: "mrkdwn",
text: `*Author:*\n${meta.githubCommitAuthorName || "N/A"}`
}
]
},
{
type: "section",
text: {
type: "mrkdwn",
text: `*Commit:* ${meta.githubCommitMessage || "N/A"}`
}
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `<${deployment.inspectorUrl || "#"}|View Deployment> | ${deployment.url}`
}
]
}
]
};
}Database Format
function transform(payload) {
const deployment = payload.payload.deployment;
const project = payload.payload.project;
const team = payload.payload.team;
const meta = deployment.meta || {};
return {
vercel_event_id: payload.id,
event_type: payload.type,
deployment_id: deployment.id,
deployment_url: deployment.url,
project_id: project.id,
project_name: project.name,
team_slug: team ? team.slug : null,
target: deployment.target || "preview",
ready_state: deployment.readyState || null,
error_message: deployment.errorMessage || null,
git_sha: meta.githubCommitSha || null,
git_branch: meta.githubCommitRef || null,
git_message: meta.githubCommitMessage || null,
git_author: meta.githubCommitAuthorName || null,
git_repo: meta.githubRepo ? `${meta.githubOrg}/${meta.githubRepo}` : null,
created_at: new Date(payload.createdAt).toISOString()
};
}Alert for Failed Deployments
function transform(payload) {
const deployment = payload.payload.deployment;
const project = payload.payload.project;
const meta = deployment.meta || {};
return {
channel: "#deploy-alerts",
username: "Vercel Bot",
icon_emoji: ":warning:",
attachments: [{
color: "danger",
title: `Deployment Failed: ${project.name}`,
fields: [
{ title: "Branch", value: meta.githubCommitRef || "N/A", short: true },
{ title: "Target", value: deployment.target || "preview", short: true },
{ title: "Author", value: meta.githubCommitAuthorName || "N/A", short: true },
{ title: "Commit", value: meta.githubCommitMessage || "N/A", short: false },
{ title: "Error", value: deployment.errorMessage || "Unknown error", short: false }
],
footer: `Deployment ${deployment.id}`,
ts: Math.floor(payload.createdAt / 1000)
}]
};
}Filter Examples
Production Deployments Only
{
"name": "Production Only",
"logic": "and",
"conditions": [
{
"field": "payload.deployment.target",
"operator": "equals",
"value": "production"
}
]
}Deployment Events
{
"name": "Deployment Events",
"logic": "or",
"conditions": [
{
"field": "type",
"operator": "starts_with",
"value": "deployment"
}
]
}Failed Deployments
{
"name": "Failed Deployments",
"logic": "and",
"conditions": [
{
"field": "type",
"operator": "equals",
"value": "deployment.error"
}
]
}Specific Project
{
"name": "My App Deployments",
"logic": "and",
"conditions": [
{
"field": "payload.project.name",
"operator": "equals",
"value": "my-app"
},
{
"field": "type",
"operator": "starts_with",
"value": "deployment"
}
]
}Headers
Vercel sends the following headers with each webhook request:
| Header | Description | Example |
|---|---|---|
x-vercel-signature | HMAC-SHA1 hex digest of the request body | a3f7b9c2e1d4f6... |
content-type | Always application/json | application/json |
user-agent | Vercel webhook user agent | Vercel-Webhook/1.0 |
Testing
Send a Test Event
From the Vercel Dashboard webhook settings, you can send a test event to verify your integration is working.
Create a Test Source
Create a separate source for development and staging environments:
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Vercel Staging",
"slug": "vercel-staging",
"verificationConfig": {
"type": "hmac",
"secret": "your-staging-webhook-secret",
"algorithm": "sha1",
"header": "x-vercel-signature",
"encoding": "hex"
}
}'Verify with cURL
Test signature verification manually:
SECRET="your-vercel-webhook-secret"
BODY='{"type":"deployment.created","payload":{"deployment":{"id":"dpl_test"}}}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha1 -hmac "$SECRET" | awk '{print $2}')
curl -X POST https://api.hookbase.app/ingest/{orgSlug}/vercel \
-H "Content-Type: application/json" \
-H "x-vercel-signature: $SIGNATURE" \
-d "$BODY"Best Practices
Separate environments: Use different webhook endpoints and sources for production and preview deployments
Filter by target: Use filters to route production and preview deployment events to different destinations
Handle idempotency: Use
payload.id(the event ID) to prevent duplicate processingMonitor build errors: Set up alerts for
deployment.errorevents to catch build failures quicklyTrack git metadata: Store commit SHA and branch information for deployment traceability
Scope to projects: If you have many Vercel projects, use filters to route events per project to the appropriate handlers
Troubleshooting
Signature Verification Failed
- Verify the webhook secret in Hookbase matches the secret configured in Vercel Dashboard
- Ensure the verification config uses
algorithm: "sha1"andencoding: "hex" - Confirm the header is set to
x-vercel-signature(case-sensitive in config) - Check that the secret has no leading or trailing whitespace
Missing Events
- Check which events are selected in Vercel webhook settings
- Verify filters are not blocking expected events
- Check the Vercel Dashboard webhook logs for delivery attempts and response codes
- Ensure your Hookbase source slug matches the URL path
Webhook Not Triggering
- Confirm the webhook is enabled in Vercel Dashboard
- Verify the endpoint URL is correct and publicly accessible
- Check that the selected events match the actions you are performing (e.g., deploying to production vs. preview)
Duplicate Events
- Vercel may send the same event more than once in rare cases
- Implement idempotency checks using the event
idfield - Check for multiple webhook endpoints configured for the same events