Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Skills for receiving and verifying webhooks from specific providers. Each includ
| SendGrid | [`sendgrid-webhooks`](skills/sendgrid-webhooks/) | Verify SendGrid webhook signatures (ECDSA), handle email delivery events |
| Shopify | [`shopify-webhooks`](skills/shopify-webhooks/) | Verify Shopify HMAC signatures, handle order and product webhook events |
| Stripe | [`stripe-webhooks`](skills/stripe-webhooks/) | Verify Stripe webhook signatures, parse payment event payloads, handle checkout.session.completed events |
| Vercel | [`vercel-webhooks`](skills/vercel-webhooks/) | Verify Vercel webhook signatures (HMAC-SHA1), handle deployment and project events |

### Webhook Handler Pattern Skills

Expand Down
14 changes: 14 additions & 0 deletions providers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,20 @@ providers:
- payment_intent.succeeded
- checkout.session.completed

- name: vercel
displayName: Vercel
docs:
webhooks: https://vercel.com/docs/observability/webhooks
events: https://vercel.com/docs/observability/webhooks#event-types
notes: >
Platform for deploying web applications. Uses x-vercel-signature header with HMAC-SHA1
(hex encoded). Uses raw request body for signature verification.
Common events: deployment.created, deployment.succeeded, project.created.
testScenario:
events:
- deployment.created
- deployment.succeeded

- name: hookdeck-event-gateway
displayName: Hookdeck Event Gateway
docs:
Expand Down
23 changes: 15 additions & 8 deletions scripts/validate-provider.sh
Original file line number Diff line number Diff line change
Expand Up @@ -212,20 +212,28 @@ validate_integration() {
errors+=("$provider not found in README.md Provider Skills table")
fi

# Check providers.yaml has entry
# Check providers.yaml has entry with testScenario
# test-agent-scenario.sh now reads scenarios dynamically from providers.yaml
if [ -f "$ROOT_DIR/providers.yaml" ]; then
if ! grep -q "name: $provider_name" "$ROOT_DIR/providers.yaml"; then
errors+=("$provider_name not found in providers.yaml")
else
# Check that the provider has a testScenario defined
# Use awk to find the provider block and check for testScenario
local has_test_scenario
has_test_scenario=$(awk -v provider="$provider_name" '
/^ - name:/ { in_provider = ($3 == provider) }
in_provider && /testScenario:/ { print "yes"; exit }
/^ - name:/ && !($3 == provider) { in_provider = 0 }
' "$ROOT_DIR/providers.yaml")
if [ "$has_test_scenario" != "yes" ]; then
errors+=("No testScenario for $provider_name in providers.yaml")
fi
fi
else
errors+=("providers.yaml not found at repository root")
fi

# Check test-agent-scenario.sh has at least one scenario
if ! grep -q "$provider_name" "$ROOT_DIR/scripts/test-agent-scenario.sh"; then
errors+=("No scenario for $provider_name in scripts/test-agent-scenario.sh")
fi

# Return errors
if [ ${#errors[@]} -gt 0 ]; then
printf '%s\n' "${errors[@]}"
Expand Down Expand Up @@ -324,8 +332,7 @@ if [ ${#FAILED_PROVIDERS[@]} -gt 0 ]; then
log "Please ensure you have updated:"
log " 1. All required skill files (SKILL.md, references/, examples/)"
log " 2. README.md - Add provider to Provider Skills table"
log " 3. providers.yaml - Add provider entry with documentation URLs"
log " 4. scripts/test-agent-scenario.sh - Add at least one test scenario"
log " 3. providers.yaml - Add provider entry with documentation URLs and testScenario"
exit 1
else
log "${GREEN}All ${#PASSED_PROVIDERS[@]} provider(s) passed validation!${NC}"
Expand Down
210 changes: 210 additions & 0 deletions skills/vercel-webhooks/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
---
name: vercel-webhooks
description: >
Receive and verify Vercel webhooks. Use when setting up Vercel webhook
handlers, debugging signature verification, or handling deployment events
like deployment.created, deployment.succeeded, or project.created.
license: MIT
metadata:
author: hookdeck
version: "0.1.0"
repository: https://github.com/hookdeck/webhook-skills
---

# 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

## Essential Code (USE THIS)

### Express Webhook Handler with Manual Verification

```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
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/](examples/express/) - Full Express implementation
> - [examples/nextjs/](examples/nextjs/) - Next.js App Router implementation
> - [examples/fastapi/](examples/fastapi/) - Python FastAPI implementation

## Common Event Types

| Event | Triggered When | Common Use Cases |
|-------|----------------|------------------|
| `deployment.created` | A new deployment starts | Start deployment monitoring, notify team |
| `deployment.succeeded` | Deployment completes successfully | Update status, trigger post-deploy tasks |
| `deployment.error` | Deployment fails | Alert team, rollback actions |
| `deployment.canceled` | Deployment is canceled | Clean up resources |
| `project.created` | New project is created | Set up monitoring, configure resources |
| `project.removed` | Project is deleted | Clean up external resources |
| `domain.created` | Domain is added | Update DNS, SSL configuration |

See [references/overview.md](references/overview.md) for the complete event list.

## Environment Variables

```bash
# Required
VERCEL_WEBHOOK_SECRET=your_webhook_secret_from_dashboard

# Optional (for API calls)
VERCEL_TOKEN=your_vercel_api_token
```

## Local Development

For local webhook testing, install Hookdeck CLI:

```bash
# Install via npm
npm install -g hookdeck-cli

# 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.

## Reference Materials

- [Webhook Overview](references/overview.md) - What Vercel webhooks are, all event types
- [Setup Guide](references/setup.md) - Configure webhooks in Vercel dashboard
- [Signature Verification](references/verification.md) - SHA1 HMAC verification details

## Recommended: webhook-handler-patterns

For production-ready webhook handling, also install the `webhook-handler-patterns` skill to learn:

- [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md) - Correct order of operations
- [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md) - Handle duplicate webhooks
- [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md) - Proper status codes and retries
- [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md) - Handle transient failures

## Related Skills

- [stripe-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks) - Stripe payment webhooks
- [github-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/github-webhooks) - GitHub repository webhooks
- [shopify-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/shopify-webhooks) - Shopify store webhooks
- [sendgrid-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks) - SendGrid email webhooks
- [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) - Idempotency, error handling, retry logic
- [hookdeck-event-gateway](https://github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway) - Production webhook infrastructure
27 changes: 27 additions & 0 deletions skills/vercel-webhooks/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# TODO - Known Issues and Improvements

*Last updated: 2026-02-04*

These items were identified during automated review but are acceptable for merge.
Contributions to address these items are welcome.

## Issues

### Major

- [ ] **skills/vercel-webhooks/references/overview.md**: The 'attack.detected' event is included but marked as potentially requiring specific plans or security features. Without official documentation confirmation, this could mislead users
- Suggested fix: Either verify this event exists in Vercel's documentation or remove it entirely to avoid confusion

### Minor

- [ ] **skills/vercel-webhooks/examples/express/package.json**: Using Express 5.x (^5.2.1) which is newer. Most production apps still use Express 4.x
- Suggested fix: Consider using Express ^4.21.2 for broader compatibility, or add a note about Express 5.x being used
- [ ] **skills/vercel-webhooks/examples/nextjs/package.json**: Next.js version 16.1.6 doesn't exist. The latest stable version is around 14.x or 15.x
- Suggested fix: Use a valid Next.js version like ^14.2.0 or ^15.0.0

## Suggestions

- [ ] Consider adding rate limiting recommendations in the documentation for production deployments
- [ ] The webhook security documentation could mention that the signature verification details are based on common patterns, as Vercel's official docs don't explicitly detail the verification method
- [ ] Consider adding a note about webhook retry behavior and idempotency handling best practices

5 changes: 5 additions & 0 deletions skills/vercel-webhooks/examples/express/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copy this file to .env and add your Vercel webhook secret
VERCEL_WEBHOOK_SECRET=your_webhook_secret_from_vercel_dashboard

# Server port
PORT=3000
Loading