WEBHOOKS
The --webhook flag on swamp serve registers an HTTP endpoint that triggers a
workflow when an incoming request arrives. The flag is repeatable — pass it
multiple times to register several endpoints.
Spec format
On the CLI, webhooks use a colon-delimited string:
--webhook '<route>:<workflow>:<secret>[:<scheme>[:<header>[:<prefix>]]]'In a config file, each webhook is a YAML object with named fields instead of the colon-delimited format.
| Field | Required | Description |
|---|---|---|
route |
Yes | URL path for the endpoint (e.g., /hooks/github) |
workflow |
Yes | Workflow to trigger on valid requests |
secret |
Yes | Signing secret or secret indirection reference |
scheme |
No | Provider scheme (default: github). See Provider schemes |
header |
No | Custom header name (generic scheme only) |
prefix |
No | Expected value prefix (generic scheme only) |
Provider schemes
Each scheme defines how the incoming request signature is verified.
| Scheme | Algorithm | Signature header | Notes |
|---|---|---|---|
github |
HMAC-SHA256 | X-Hub-Signature-256 |
Default when scheme is omitted |
linear |
HMAC-SHA256 | Linear-Signature |
|
stripe |
HMAC-SHA256 | Stripe-Signature |
Stripe signing protocol (timestamp + payload) |
slack |
HMAC-SHA256 | X-Slack-Signature |
|
jira |
HMAC-SHA256 | X-Hub-Signature |
sha256= prefix (same HMAC as GitHub) |
generic |
HMAC-SHA256 | Custom (via <header>) |
Requires a header name; optional prefix |
Secret sources
The <secret> field supports indirection so signing secrets do not appear as
literal values in process arguments.
| Prefix | Source | Example |
|---|---|---|
@env=VAR_NAME |
Environment variable VAR_NAME |
@env=WEBHOOK_SECRET |
@file=/path |
File contents (trailing newline trimmed) | @file=/run/secrets/webhook |
@vault=<name>:<key> |
Named vault key | @vault=prod-secrets:webhook-key |
| (plain string) | Literal value (backward compatible) | mysecret |
The server reads the resolved secret at startup. If the environment variable is unset or the file cannot be read, the server refuses to start.
CEL context
When a workflow is triggered by a webhook, the webhook.* namespace is
available in CEL expressions within that workflow.
| Field | Type | Description |
|---|---|---|
webhook.body |
dyn |
Parsed JSON body (object or array), or raw string for non-JSON payloads |
webhook.headers |
map<string, string> |
Request headers |
webhook.route |
string |
Matched route path |
webhook.body.action == "opened"
webhook.headers["X-GitHub-Event"] == "issues"Examples
GitHub (default scheme)
swamp serve --webhook '/hooks/github:on-push:@env=GITHUB_WEBHOOK_SECRET'Linear
swamp serve --webhook '/hooks/linear:on-issue:@env=LINEAR_SECRET:linear'Stripe
swamp serve --webhook '/hooks/stripe:on-payment:@env=STRIPE_WEBHOOK_SECRET:stripe'Slack
swamp serve --webhook '/hooks/slack:on-command:@env=SLACK_SIGNING_SECRET:slack'Jira
swamp serve --webhook '/hooks/jira:on-issue:@env=JIRA_WEBHOOK_SECRET:jira'Generic
swamp serve --webhook '/hooks/custom:on-event:@env=CUSTOM_SECRET:generic:X-Custom-Token:Bearer'The generic scheme computes an HMAC-SHA256 digest of the request body using
the resolved secret as the key, then compares it against the value of the
X-Custom-Token header (after stripping the Bearer prefix).
Extension webhook schemes
Webhook schemes of the form @collective/name are resolved from installed
extensions. The extension must export a webhook handler (see
Webhook kind). Trusted
collectives are auto-pulled on first reference.
serve.yaml format
webhooks:
- route: /hooks/telegram
workflow: on-message
secret: "@env=TELEGRAM_SECRET"
scheme: "@swamp/telegram"
config:
botName: my-botThe config object is passed to the extension handler's
createHandler(config). When configSchema is declared in the handler, the
config object is validated at startup.
CLI format
swamp serve --webhook '/hooks/telegram:on-message:@env=TELEGRAM_SECRET:@swamp/telegram'The CLI format does not support the config object. Use a config file when the
handler requires configuration.
Handler lifecycle
- The server resolves the extension scheme and calls
createHandler(config). - On each incoming request,
verify(body, headers, secret)runs first. Afalsereturn or a thrown error responds 401. Verification is fail-closed: any error rejects the request. - Hooks fire only after successful verification — a failed verify never triggers hooks.
- If
transform(body, redactedHeaders)is defined, the transformed payload replaces the raw body for downstream processing. - If
respond(payload)is defined, its return value ({ status, headers?, body?, enqueue }) controls the HTTP response and whether the workflow run is enqueued. Withoutrespond, the default 200 response and enqueue behaviour applies.
There is no handler timeout — the server does not impose a deadline on verify,
transform, or respond. Response headers returned by respond are passed
through unfiltered.
@swamp/telegram
First-party extension for receiving Telegram bot updates.
Telegram uses a static secret token sent in the
X-Telegram-Bot-Api-Secret-Token header. The handler compares it to the
configured secret using constant-time comparison. This is weaker than HMAC — the
secret is transmitted in the clear (not derived from the body), so TLS is
required. Do not use over plain HTTP.
webhooks:
- route: /hooks/telegram
workflow: on-telegram
secret: "@env=TELEGRAM_BOT_SECRET"
scheme: "@swamp/telegram"Set the webhook on the Telegram side with the matching secret_token:
curl -X POST "https://api.telegram.org/bot$BOT_TOKEN/setWebhook" \
-d "url=https://swamp.example.com/hooks/telegram" \
-d "secret_token=$TELEGRAM_BOT_SECRET"Related
- Serve Flags — full flag reference
for
swamp serve - Vaults — vault types, creation, and key management
- Extension Manifest — Webhooks — webhook extension handler interface