Gareth Wilson Gareth Wilson

Guide to Vercel Webhooks: Features and Best Practices

Published


Vercel webhooks notify your application about deployment and project activity: a deployment succeeds or errors, a project is created or removed, a domain is added. If you're building on Vercel, webhooks are how you react to these events without polling.

This guide covers how Vercel webhooks work, the events you'll handle, how to verify the x-vercel-signature (it's SHA-1, not SHA-256), the three places the secret can come from, and the best practices for production.

What are Vercel webhooks?

Vercel webhooks are JSON POSTs delivered to a URL you configure. Each is signed with an x-vercel-signature header: an HMAC-SHA1 (not SHA-256) over the raw request body, hex-encoded. Assuming SHA-256 is the single most likely mistake here. The signed input is the raw body only, with no timestamp concatenated.

Vercel webhook features

FeatureDetails
ConfigurationAccount/team settings > Webhooks (limit of 20 per team)
Signature headerx-vercel-signature (lowercase)
Signature schemeHMAC-SHA1 (hex) over the raw body
Secret sourcesTeam webhook secret, Integration client secret, or Log Drain secret (all in the same header)
Event fieldTop-level type (dotted)
RetriesNot documented
SDKnpm @vercel/sdk (REST client, no verification helper); no official pip

Common events

Vercel reports the event type in the top-level type field (dotted):

EventFires when
deployment.created / deployment.succeeded / deployment.errorA deployment changes state
deployment.canceled / deployment.promotedA deployment is canceled or promoted
project.created / project.removedA project is created or removed
domain.createdA domain is added
integration-configuration.removedAn integration configuration is removed

The envelope carries id, type, createdAt, region, and payload.

See Vercel webhook payloads in action. Inspect and replay sample Vercel webhook payloads in the Hookdeck Console — no account or setup required.

Setting up Vercel webhooks

Where the secret comes from depends on the webhook type, and all three arrive in the same x-vercel-signature header. Team/account webhooks (Account or team settings > Webhooks) show their secret exactly once at creation. Integration webhooks use the Integration's Client Secret. Log Drains, a distinct mechanism documented separately, use their own Drain signature secret. Store the relevant one as VERCEL_WEBHOOK_SECRET.

Securing Vercel webhooks

Compute an HMAC-SHA1 (not SHA-256) over the raw body, hex-encode it, and compare against x-vercel-signature in constant time. Verify against the raw body before parsing, on Next.js this needs a little care (see below).

const crypto = require("crypto");

const SECRET = process.env.VERCEL_WEBHOOK_SECRET;

function verify(rawBody, signature) {
  if (!signature) return false;
  const expected = crypto.createHmac("sha1", SECRET).update(rawBody).digest("hex"); // sha1, NOT sha256
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false;
  }
}

app.post("/webhooks/vercel", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-vercel-signature"])) return res.sendStatus(401);
  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body.toString())); // branch on type, async
});

The same check in Python:

import hashlib
import hmac
import os

SECRET = os.environ["VERCEL_WEBHOOK_SECRET"].encode()


def verify(raw_body: bytes, signature: str) -> bool:
    if not signature:
        return False
    expected = hmac.new(SECRET, raw_body, hashlib.sha1).hexdigest()  # SHA-1
    return hmac.compare_digest(signature, expected)

Make Vercel webhooks production-ready. Hookdeck Event Gateway verifies the x-vercel-signature, deduplicates, and durably queues every event.

Vercel webhook limitations and pain points

It's SHA-1, not SHA-256

The Problem: x-vercel-signature is HMAC-SHA1. Reaching for SHA-256 (as Stripe and GitHub use) never matches, and it's the easiest mistake to make.

Why It Happens: Vercel signs with SHA-1.

Workarounds:

  • Use sha1 in the HMAC, hex, over the raw body.

How Hookdeck Can Help: Hookdeck verifies the signature at the edge with the right algorithm, so your app doesn't hard-code it.

The secret comes from three places

The Problem: Team webhooks, Integration webhooks, and Log Drains each use a different secret, all delivered in the same x-vercel-signature header. Using the wrong one fails verification.

Why It Happens: Vercel scopes the signing secret by webhook type.

Workarounds:

  • Use the team webhook secret (shown once at creation), the Integration client secret, or the Log Drain secret, matching the webhook's type.

How Hookdeck Can Help: Hookdeck can verify each source with its own secret, so you don't juggle three in one handler.

Raw-body handling on Next.js

The Problem: Frameworks that parse JSON before you verify break the signature. On Vercel's own Next.js this bites: the Pages Router parses the body by default.

Why It Happens: Automatic body parsing re-serializes the payload.

Workarounds:

  • Next.js Pages Router: set export const config = { api: { bodyParser: false } } and read the raw body (for example with the raw-body package). App Router: await request.text().

How Hookdeck Can Help: Hookdeck verifies before forwarding, so your framework's body parsing can't invalidate the check.

Retries aren't documented, and Log Drains are separate

The Problem: Vercel doesn't document a retry schedule, so you can't rely on redelivery. And Log Drains are a distinct mechanism, not platform webhooks, so conflating them leads to wrong assumptions.

Why It Happens: Retry behavior is unpublished; Log Drains are a separate product (with their own Hookdeck source type).

Workarounds:

  • Don't depend on redelivery, make handlers idempotent, and treat Log Drains separately.

How Hookdeck Can Help: Hookdeck durably queues and retries on its own schedule, so delivery doesn't depend on Vercel's unpublished behavior.

Best practices

Verify HMAC-SHA1 over the raw body

Compute the hex HMAC-SHA1 over the raw body and compare against x-vercel-signature in constant time.

Use the right secret for the webhook type

Team webhook secret, Integration client secret, or Log Drain secret, matched to the source.

Preserve the raw body on Next.js

Disable body parsing (Pages Router) or use request.text() (App Router) so verification sees the exact bytes.

Acknowledge fast, process asynchronously

Return 200 quickly and defer work to a queue; make handlers idempotent since retries aren't documented. See why to process webhooks asynchronously.

Conclusion

Vercel webhooks are verified with an x-vercel-signature HMAC-SHA1 (not SHA-256) over the raw body, hex-encoded. The secret comes from one of three places depending on webhook type, all in the same header. Use SHA-1, preserve the raw body (especially on Next.js), make handlers idempotent since retries aren't documented, and treat Log Drains as a separate mechanism.

Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.

Get started with Hookdeck for free and handle Vercel webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.