Self-hosted · one container
Vercel events, in the channels you already watch.
Versend verifies the webhook signature, applies your filters and posts a clean card to Discord, Telegram, Slack, ntfy or Gotify. No database, no dashboard, no account.
services:
versend:
image: ghcr.io/versend/versend:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
WEBHOOK_INTEGRATION_SECRET: ${WEBHOOK_INTEGRATION_SECRET}
DISCORD_WEBHOOK_URL: ${DISCORD_WEBHOOK_URL}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID}
FILTER_EVENTS: deployment.succeeded,deployment.error$ docker compose up -d ✔ Container versend Started$ curl -s localhost:3000/health{ "ok": true, "destinations": ["discord", "telegram"] }
01How it works
One request in, one card out per channel.
The relay is a single Bun process behind one route. There is nothing to migrate, back up or log into.
01
Verify
Every request is checked against WEBHOOK_INTEGRATION_SECRET before anything is parsed. A bad signature is a 401, not a message.
02
Filter
FILTER_EVENTS and FILTER_EVENTS_EXCLUDE pick event types, FILTER_TARGETS picks deployment targets. A per-IP rate limit keeps the relay calm.
03
Deliver
The event is turned into one card and posted to every configured destination in parallel. 429s and 5xxs are retried with backoff; one slow channel never blocks another.
02What lands in the channel
The same event, drawn for each destination.
Pick an event and a destination. Every card carries the project, target, branch, commit and message, plus the build error when there is one.
{ "type": "deployment.succeeded","id": "dpl_8f2a","project": "acme-web","target": "production","branch": "main","commit": "a1b2c3d","message": "Ship the billing page",…}
Versend appToday
acme-web
Deployment ready
Ship the billing page
Invoice download and the failed-payment state.
- Environment
- production
- Branch
- main
- Commit
- a1b2c3d
dpl_8f2a · deployment.succeeded
03Destinations
Five channels. Set a variable, it is on.
Discord
A colored embed with inline fields and a footer. Username and avatar are yours to set.
DISCORD_WEBHOOK_URL
GuideTelegram
An HTML message from your bot into any chat or channel it belongs to.
TELEGRAM_BOT_TOKEN · TELEGRAM_CHAT_ID
GuideSlack
An incoming webhook message with the same fields, rendered in mrkdwn.
SLACK_WEBHOOK_URL
Guidentfy
A markdown push with a priority that follows the outcome and a click-through to the deployment.
NTFY_URL · NTFY_TOKEN
GuideGotify
A markdown message to your own Gotify server and whichever app token you choose.
GOTIFY_URL · GOTIFY_TOKEN
Guide
04Why a relay
Small on purpose.
Vercel already knows what happened. Versend only has to prove the request is real, decide if you care, and say it once in each place. Everything else was left out.
- Signed
- HMAC check on every request with your integration secret.
- Filtered
- Allow or exclude event types; restrict deployment targets.
- Patient
- Per-IP rate limit and retries with backoff on 429 and 5xx.
- Stateless
- No database. Configuration is environment variables only.
- Portable
- One image for amd64 and arm64, published on every push to master.
Point the webhook at it.
Run the container, paste the URL into a Vercel webhook, and the next deployment shows up where your team is already looking.