Skip to content

Vercel Integration

Receive and route Vercel webhooks for deployments, domains, projects, and other platform events.

Setup

1. Create a Source in Hookbase

bash
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}/vercel

2. Configure Vercel Webhook

  1. Go to Vercel Dashboard and select your team
  2. Navigate to SettingsWebhooks
  3. Click Create Webhook
  4. Enter your Hookbase URL as the Endpoint URL
  5. Enter a Secret — this must match the secret in your Hookbase source
  6. Select the events you want to receive (e.g., deployment.created, deployment.succeeded)
  7. Click Create Webhook

3. Create Routes

bash
# 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: a3f7b9c2e1d4f6a8b0c3e5d7f9a1b3c5d7e9f0a2

Hookbase automatically verifies this when configured:

json
{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-vercel-webhook-secret",
    "algorithm": "sha1",
    "header": "x-vercel-signature",
    "encoding": "hex"
  }
}

How Verification Works

  1. Hookbase receives the raw request body
  2. Computes HMAC-SHA1(secret, rawBody) and hex-encodes the result
  3. Compares the computed value against the x-vercel-signature header
  4. Rejects the webhook if the signatures do not match

Common Events

deployment.created

Triggered when a new deployment is initiated.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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

javascript
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

javascript
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

javascript
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

json
{
  "name": "Production Only",
  "logic": "and",
  "conditions": [
    {
      "field": "payload.deployment.target",
      "operator": "equals",
      "value": "production"
    }
  ]
}

Deployment Events

json
{
  "name": "Deployment Events",
  "logic": "or",
  "conditions": [
    {
      "field": "type",
      "operator": "starts_with",
      "value": "deployment"
    }
  ]
}

Failed Deployments

json
{
  "name": "Failed Deployments",
  "logic": "and",
  "conditions": [
    {
      "field": "type",
      "operator": "equals",
      "value": "deployment.error"
    }
  ]
}

Specific Project

json
{
  "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:

HeaderDescriptionExample
x-vercel-signatureHMAC-SHA1 hex digest of the request bodya3f7b9c2e1d4f6...
content-typeAlways application/jsonapplication/json
user-agentVercel webhook user agentVercel-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:

bash
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:

bash
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

  1. Separate environments: Use different webhook endpoints and sources for production and preview deployments

  2. Filter by target: Use filters to route production and preview deployment events to different destinations

  3. Handle idempotency: Use payload.id (the event ID) to prevent duplicate processing

  4. Monitor build errors: Set up alerts for deployment.error events to catch build failures quickly

  5. Track git metadata: Store commit SHA and branch information for deployment traceability

  6. Scope to projects: If you have many Vercel projects, use filters to route events per project to the appropriate handlers

Troubleshooting

Signature Verification Failed

  1. Verify the webhook secret in Hookbase matches the secret configured in Vercel Dashboard
  2. Ensure the verification config uses algorithm: "sha1" and encoding: "hex"
  3. Confirm the header is set to x-vercel-signature (case-sensitive in config)
  4. Check that the secret has no leading or trailing whitespace

Missing Events

  1. Check which events are selected in Vercel webhook settings
  2. Verify filters are not blocking expected events
  3. Check the Vercel Dashboard webhook logs for delivery attempts and response codes
  4. Ensure your Hookbase source slug matches the URL path

Webhook Not Triggering

  1. Confirm the webhook is enabled in Vercel Dashboard
  2. Verify the endpoint URL is correct and publicly accessible
  3. Check that the selected events match the actions you are performing (e.g., deploying to production vs. preview)

Duplicate Events

  1. Vercel may send the same event more than once in rare cases
  2. Implement idempotency checks using the event id field
  3. Check for multiple webhook endpoints configured for the same events

Released under the MIT License.