Skip to content

Commit 92bccb9

Browse files
committed
feat: add Generic Webhook provider and enhance filtering capabilities
- Introduced a new Generic Webhook provider to forward Vercel events to any URL, including setup instructions and payload format documentation. - Added environment variables WEBHOOK_URL and FILTER_TARGETS to support the new provider and allow filtering of deployment targets. - Updated webhook processing logic to include target filtering, ensuring notifications are sent only for specified deployment targets. - Enhanced documentation to reflect the addition of the Generic Webhook provider and its configuration.
1 parent 3d3082d commit 92bccb9

File tree

15 files changed

+272
-5
lines changed

15 files changed

+272
-5
lines changed

apps/fumadocs/content/docs/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ A lightweight webhook relay that sends Vercel deployment notifications wherever
2525
| [Discord](/docs/providers/discord) | Rich embeds with colors and fields |
2626
| [Slack](/docs/providers/slack) | Block Kit messages via webhooks |
2727
| [Telegram](/docs/providers/telegram) | HTML messages via Bot API |
28+
| [Generic Webhook](/docs/providers/webhook) | Raw JSON to any URL |
2829

2930
## Quick Start
3031

apps/fumadocs/content/docs/installation/environment.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ Add the variables for at least one provider. See the [Providers](/docs/providers
7575
}}
7676
/>
7777

78+
**Generic Webhook:**
79+
80+
<TypeTable
81+
type={{
82+
WEBHOOK_URL: {
83+
description: 'URL to forward raw webhook payloads to',
84+
type: 'string',
85+
},
86+
}}
87+
/>
88+
7889
</Step>
7990
<Step>
8091

@@ -119,6 +130,25 @@ If set, only the specified events will trigger notifications. If not set, all ev
119130
</Step>
120131
<Step>
121132

133+
### Add Target Filtering (Optional)
134+
135+
<TypeTable
136+
type={{
137+
FILTER_TARGETS: {
138+
description: 'Comma-separated list of deployment targets to allow',
139+
type: 'string',
140+
default: 'all targets',
141+
},
142+
}}
143+
/>
144+
145+
Example: `FILTER_TARGETS=production`
146+
147+
If set, only deployments to the specified targets will trigger notifications. Common values: `production`, `preview`.
148+
149+
</Step>
150+
<Step>
151+
122152
### Redeploy
123153

124154
Go to **Deployments** and click the three dots on the latest deployment →

apps/fumadocs/content/docs/installation/setup-vercel.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,16 @@ After creating the webhook, Vercel will show you a **signing secret**.
4444

4545
</Step>
4646
</Steps>
47+
48+
## Filtering by Project
49+
50+
You can filter which projects trigger notifications by choosing where to create the webhook:
51+
52+
- **Account/Team level**: Receives events from **all** projects in the account/team
53+
- **Project level**: Receives events from **only that project**
54+
55+
<Callout type="warn">
56+
Each Versend instance can only have **one** webhook secret. If you need to monitor multiple specific projects (but not all), create the webhook at the account/team level and all projects will be monitored.
57+
</Callout>
58+
59+
For single-project monitoring, create the webhook directly in that project's settings.

apps/fumadocs/content/docs/providers/index.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Versend supports multiple notification providers. Configure one or many — noti
1515
| [Discord](/docs/providers/discord) | ✅ Stable | Rich embeds with colors, fields, and links |
1616
| [Slack](/docs/providers/slack) | ✅ Stable | Block Kit messages via incoming webhooks |
1717
| [Telegram](/docs/providers/telegram) | ✅ Stable | HTML-formatted messages via Bot API |
18+
| [Generic Webhook](/docs/providers/webhook) | ✅ Stable | Forward raw payload to any URL |
1819

1920
## How It Works
2021

@@ -23,6 +24,7 @@ Each provider is **auto-enabled** when its required environment variables are se
2324
- **Discord**: Set `DISCORD_WEBHOOK_URL` → Discord enabled
2425
- **Slack**: Set `SLACK_WEBHOOK_URL` → Slack enabled
2526
- **Telegram**: Set `TELEGRAM_BOT_TOKEN` + `TELEGRAM_CHAT_ID` → Telegram enabled
27+
- **Webhook**: Set `WEBHOOK_URL` → Generic webhook enabled
2628

2729
Enable as many as you need.
2830

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"title": "Providers",
3-
"pages": ["index", "discord", "slack", "telegram"]
3+
"pages": ["index", "discord", "slack", "telegram", "webhook"]
44
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
title: Generic Webhook
3+
description: Forward Vercel events to any URL
4+
icon: Globe
5+
---
6+
7+
import { Step, Steps } from 'fumadocs-ui/components/steps';
8+
import { TypeTable } from 'fumadocs-ui/components/type-table';
9+
10+
# Generic Webhook Provider
11+
12+
Forward the raw Vercel webhook payload to any HTTP endpoint. Perfect for integrating with Zapier, n8n, Make, or your own services.
13+
14+
## Features
15+
16+
- Forwards the complete Vercel webhook payload as JSON
17+
- Adds `X-Versend-Event` header with the event type
18+
- Automatic retry with backoff for failures
19+
- Works with any HTTP endpoint that accepts POST requests
20+
21+
## Setup
22+
23+
<Steps>
24+
<Step>
25+
26+
### Get Your Webhook URL
27+
28+
Set up an endpoint to receive webhooks. This could be:
29+
30+
- **Zapier**: Create a Zap with "Webhooks by Zapier" trigger
31+
- **n8n**: Create a workflow with "Webhook" trigger node
32+
- **Make**: Create a scenario with "Webhooks" module
33+
- **Custom**: Any URL that accepts POST requests with JSON body
34+
35+
</Step>
36+
<Step>
37+
38+
### Add Environment Variable
39+
40+
Add this to your Vercel project:
41+
42+
<TypeTable
43+
type={{
44+
WEBHOOK_URL: {
45+
description: 'URL to forward webhooks to',
46+
type: 'string',
47+
required: true,
48+
},
49+
}}
50+
/>
51+
52+
</Step>
53+
<Step>
54+
55+
### Redeploy
56+
57+
Redeploy your Versend instance to apply the changes.
58+
59+
</Step>
60+
</Steps>
61+
62+
## Payload Format
63+
64+
The endpoint receives the raw Vercel webhook payload:
65+
66+
```json
67+
{
68+
"id": "webhook_xxx",
69+
"type": "deployment.succeeded",
70+
"createdAt": 1234567890,
71+
"payload": {
72+
"deployment": { ... },
73+
"links": { ... },
74+
...
75+
}
76+
}
77+
```
78+
79+
## Headers
80+
81+
Each request includes:
82+
83+
| Header | Description |
84+
|--------|-------------|
85+
| `Content-Type` | `application/json` |
86+
| `User-Agent` | `Versend/1.0` |
87+
| `X-Versend-Event` | Event type (e.g., `deployment.succeeded`) |
88+
89+
## Use Cases
90+
91+
- **Zapier/Make automation**: Trigger workflows on deployments
92+
- **Custom dashboards**: Aggregate deployment data
93+
- **Audit logging**: Store all deployment events
94+
- **Multi-service fanout**: Forward to multiple internal services
95+

apps/web/src/app/api/hook/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { after } from "next/server";
22
import { ZodError } from "zod/v4";
33

44
import HttpStatusCode from "@/enums/http-status-codes";
5-
import { isEventAllowed } from "@/lib/filter";
5+
import { isEventAllowed, isTargetAllowed } from "@/lib/filter";
66
import { sendNotifications } from "@/lib/notify";
77
import { checkRateLimit, getClientIp } from "@/lib/ratelimit";
88
import { verifySignature } from "@/lib/verify";
@@ -38,6 +38,10 @@ export async function POST(req: Request) {
3838
return Response.json({ success: true, message: "Event filtered" });
3939
}
4040

41+
if (!isTargetAllowed(webhook)) {
42+
return Response.json({ success: true, message: "Target filtered" });
43+
}
44+
4145
after(() => sendNotifications(webhook));
4246

4347
return Response.json({ success: true, message: "Webhook processed" });

apps/web/src/env.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ export const env = createEnv({
1212
TELEGRAM_CHAT_ID: string().optional(),
1313
// Slack
1414
SLACK_WEBHOOK_URL: string().url().optional(),
15+
// Generic Webhook
16+
WEBHOOK_URL: string().url().optional(),
1517
// Rate limiting
1618
UPSTASH_REDIS_REST_URL: string().url().optional(),
1719
UPSTASH_REDIS_REST_TOKEN: string().optional(),
1820
// Filtering
1921
FILTER_EVENTS: string().optional(),
22+
FILTER_TARGETS: string().optional(),
2023
},
2124
client: {},
2225
runtimeEnv: {
@@ -25,9 +28,11 @@ export const env = createEnv({
2528
TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
2629
TELEGRAM_CHAT_ID: process.env.TELEGRAM_CHAT_ID,
2730
SLACK_WEBHOOK_URL: process.env.SLACK_WEBHOOK_URL,
31+
WEBHOOK_URL: process.env.WEBHOOK_URL,
2832
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
2933
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
3034
FILTER_EVENTS: process.env.FILTER_EVENTS,
35+
FILTER_TARGETS: process.env.FILTER_TARGETS,
3136
},
3237
extends: [vercel()],
3338
});

apps/web/src/lib/filter.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { env } from "@/env";
2-
import type { WebhookType } from "@/schemas/vercel";
2+
import type { VercelWebhook, WebhookType } from "@/schemas/vercel";
33

44
const allowedEvents: Set<string> | null = env.FILTER_EVENTS
55
? new Set(env.FILTER_EVENTS.split(",").map((e) => e.trim()))
66
: null;
77

8+
const allowedTargets: Set<string> | null = env.FILTER_TARGETS
9+
? new Set(env.FILTER_TARGETS.split(",").map((t) => t.trim().toLowerCase()))
10+
: null;
11+
812
/**
913
* Check if an event type should trigger notifications.
1014
* If FILTER_EVENTS is not set, all events are allowed.
@@ -16,3 +20,26 @@ export function isEventAllowed(type: WebhookType): boolean {
1620
}
1721
return allowedEvents.has(type);
1822
}
23+
24+
/**
25+
* Check if a deployment target should trigger notifications.
26+
* If FILTER_TARGETS is not set, all targets are allowed.
27+
* If set, only deployments to listed targets (production, preview) are allowed.
28+
*/
29+
export function isTargetAllowed(webhook: VercelWebhook): boolean {
30+
if (!allowedTargets) {
31+
return true;
32+
}
33+
34+
// Only applies to deployment events
35+
if (!webhook.type.startsWith("deployment.")) {
36+
return true;
37+
}
38+
39+
const target =
40+
webhook.payload.deployment?.meta?.target ||
41+
webhook.payload.target ||
42+
"preview";
43+
44+
return allowedTargets.has(target.toLowerCase());
45+
}

apps/web/src/providers/discord/consts.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { WebhookType } from "@/schemas/vercel";
2-
import type { StateProperty } from "./types";
2+
3+
type StateProperty = "color" | "emoji";
34

45
export const BOT_NAME = "Versend";
56
export const BOT_AVATAR =

0 commit comments

Comments
 (0)