A Deno-based webhook middleware that receives payloads from external services and forwards them as formatted messages to Mattermost channels via incoming webhooks.
- π Routes webhooks to specific Mattermost channels
- π Token-based authentication (URL path token, supports comma-separated list for zero-downtime rotation)
- π³ Distroless production Docker image (minimal attack surface)
- π¨ Sentry β Mattermost integration (more sources coming)
POST /webhook/<source>/mattermost_<channel>/<token>
| Segment | Description |
|---|---|
source |
Webhook source (e.g. sentry) |
channel |
Mattermost channel name (lowercased) |
token |
Auth token matching WEBHOOK_TOKEN |
Example:
POST /webhook/sentry/mattermost_alerts/my-secret-token
| Variable | Required | Description |
|---|---|---|
WEBHOOK_TOKEN |
β Yes | Comma-separated list of valid auth tokens (supports rolling rotation) |
WEBHOOK_MATTERMOST_<CHANNEL>_URL |
β Yes* | Mattermost incoming webhook URL for <CHANNEL> (one per channel, uppercase) |
PORT |
No | HTTP port to listen on (default: 8000) |
*At least one WEBHOOK_MATTERMOST_*_URL is required.
Examples:
WEBHOOK_TOKEN=token-abc123,token-xyz789
WEBHOOK_MATTERMOST_ALERTS_URL=https://mattermost.example.com/hooks/xxxxxxxxxxxx
WEBHOOK_MATTERMOST_DEPLOYMENTS_URL=https://mattermost.example.com/hooks/yyyyyyyyyyyy
PORT=8000The channel name in the env var is the uppercased version of what appears in the URL:
WEBHOOK_MATTERMOST_ALERTS_URLβ/webhook/sentry/mattermost_alerts/<token>WEBHOOK_MATTERMOST_DEPLOYMENTS_URLβ/webhook/sentry/mattermost_deployments/<token>
Configure a Sentry webhook pointing to:
POST https://your-host/webhook/sentry/mattermost_<channel>/<token>
In Sentry: Project Settings β Integrations β Webhooks β Add Webhook URL.
Supported Sentry actions: created, resolved, assigned, ignored, unresolved.
The Mattermost message will include:
- Issue title (linked to Sentry)
- Action & severity level with emoji
- Culprit, project, occurrence count, affected users
- Actor name (who triggered the action)
- Deno v2.x
export WEBHOOK_TOKEN=dev-token
export WEBHOOK_MATTERMOST_ALERTS_URL=https://mattermost.example.com/hooks/xxxx
# Start with hot reload
deno task dev
# Or start normally
deno task startdeno task lint
deno fmt # auto-fix formatting
deno task fmt # check only
deno task check # type checkcurl -X POST http://localhost:8000/webhook/sentry/mattermost_alerts/dev-token \
-H "Content-Type: application/json" \
-d '{
"action": "created",
"data": {
"issue": {
"id": "123",
"title": "TypeError: Cannot read property of undefined",
"culprit": "src/app.ts in handleRequest",
"level": "error",
"status": "unresolved",
"permalink": "https://sentry.io/organizations/myorg/issues/123/",
"project": { "name": "my-app", "slug": "my-app" },
"count": "42",
"userCount": 7
}
},
"actor": { "name": "Jane Doe", "email": "jane@example.com" }
}'docker build -t sirena-tools .docker run -p 8000:8000 \
-e WEBHOOK_TOKEN=my-secret-token \
-e WEBHOOK_MATTERMOST_ALERTS_URL=https://mattermost.example.com/hooks/xxxx \
sirena-toolsThe production image is built on denoland/deno:distroless β Deno's official distroless image,
non-root, shell-less, minimal attack surface.
The chart lives in helm_charts/ and depends on SDPSN-devops-charts (same pattern as
sirena).
helm_charts/
βββ Chart.yaml # Chart metadata + SDPSN-devops-charts dependency
βββ values.yaml # Base defaults
βββ generate_manifests.sh # Render manifests for GitOps
βββ values/
β βββ sirena-tools.yaml # Main component values
β βββ external-secrets.yaml # ExternalSecret definitions
β βββ env_specific/
β βββ sirena-tools/{test,production}.yaml
β βββ external-secrets/{test,production}.yaml
βββ templates/ # Extra templates (if any)
- Helm v3
- Access to the
SDPSN-devops-chartsregistry
cd helm_charts
helm dependency update./helm_charts/generate_manifests.sh <environment> <image_tag>
# e.g.
./helm_charts/generate_manifests.sh production sha-abc1234
# dry-run (test environment renders manifests but does not apply)
./helm_charts/generate_manifests.sh test sha-abc1234Generated manifests are written to helm_charts/generated_manifests/ (gitignored).
Secrets are sourced from the secret store via the sirena-tools keystore. Required keys:
| Key | Description |
|---|---|
WEBHOOK_TOKEN |
Comma-separated auth tokens |
WEBHOOK_MATTERMOST_ALERTS_URL |
Mattermost webhook URL for alerts channel |
Add one WEBHOOK_MATTERMOST_<CHANNEL>_URL key per channel to both the secret store and
values/external-secrets.yaml.
- Replace
DNUM-SocialGouvinvalues/sirena-tools.yamlwith your GitHub org - Update the
hostin eachenv_specific/sirena-tools/<env>.yaml - Update the
namespacein eachenv_specific/file - Add/remove Mattermost channel entries in
values/external-secrets.yaml
Triggered on every push and pull request:
deno fmt --checkβ formattingdeno lintβ lintingdeno check src/main.tsβ type checking
Triggered on push to main and on semver tags (v*.*.*):
- Builds the Docker image
- Pushes to GitHub Container Registry (
ghcr.io/dnum-socialgouv/sirena-tools)
Tags applied:
| Trigger | Tags |
|---|---|
Push to main |
latest, sha-<short-sha> |
Tag v1.2.3 |
1.2.3, 1.2, 1, latest |
Add the new token to WEBHOOK_TOKEN alongside the old one, then update your webhook senders to use
the new token, then remove the old token:
# Step 1 β both tokens active
WEBHOOK_TOKEN=old-token,new-token
# Step 2 β migrate senders to new-token
# Step 3 β remove old token
WEBHOOK_TOKEN=new-token
MIT