@tagma/trigger-webhook
v0.2.108
Published
HTTP webhook trigger plugin for tagma-sdk pipelines
Maintainers
Readme
@tagma/trigger-webhook
HTTP webhook trigger plugin for @tagma/sdk.
Pauses a task until a POST request arrives on a configured local HTTP listener. Optional HMAC-SHA256 signature validation protects against unauthenticated callers.
Install
bun add @tagma/trigger-webhookUsage
pipeline:
name: deploy-on-demand
plugins:
- '@tagma/trigger-webhook'
tracks:
- id: deploy
name: Deploy
driver: claude-code
tasks:
- id: wait-signal
name: Wait for deploy signal
trigger:
type: webhook
port: 8787
path: /hooks/deploy
secret_env: TAGMA_WEBHOOK_SECRET
timeout: 2h
prompt: 'Run the deploy playbook for the staging environment'Then fire the webhook from anywhere (CI, Slack, cron, another pipeline):
BODY='{"env":"staging","sha":"abc123"}'
SIG="sha256=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$TAGMA_WEBHOOK_SECRET" | awk '{print $2}')"
curl -X POST http://localhost:8787/hooks/deploy \
-H "content-type: application/json" \
-H "x-tagma-signature: $SIG" \
-d "$BODY"Or load it programmatically:
import { createTagma } from '@tagma/sdk';
const tagma = createTagma();
await tagma.registry.loadPlugins(['@tagma/trigger-webhook'], process.cwd());Config
| Field | Type | Default | Notes |
| ---------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| port | number | (required) | TCP port to listen on (1-65535) |
| path | string | /webhook | URL path to match; must start with / |
| host | string | 127.0.0.1 | Interface to bind. Defaults to loopback. Setting to 0.0.0.0 or any non-loopback address without secret_env is refused at config time |
| secret_env | string | (none) | Env var holding the HMAC-SHA256 secret. When set, requests must include x-tagma-signature |
| max_body_bytes | number | 1048576 | Maximum accepted request body size. Larger requests return 413 payload too large before signature or JSON processing |
| timeout | duration | 2h | Max wait time; set to 0 for unbounded wait |
Behavior
- A single
Bun.servelistener is created per(host, port)pair. Different paths share that listener; each endpoint may have its ownsecret_envandmax_body_bytes. Reusing the same path with conflicting settings is rejected. Multiple tasks on one endpoint form a FIFO waiter queue; the next POST wakes one waiter. - Default bind is
127.0.0.1so the endpoint is only reachable from the local machine. A non-loopbackhostwithoutsecret_envis refused. - If
secret_envis set, the referenced environment variable must exist before the listener starts. - Request bodies are capped by
max_body_bytesfor bothContent-Lengthand streamed bodies. - Signature header format:
x-tagma-signature: sha256=<hex>, HMAC-SHA256 of the raw request body using the secret. Verification is constant-time. - JSON bodies (
content-type: application/json) are parsed before the gate fires, and malformed JSON returns400 invalid JSON body. Trigger resolution is currently gate-only in the core engine: the request body is not injected into task inputs or prompts. Other content-types are accepted as raw strings by the plugin waiter. - Successful delivery responds
202 ok. A POST arriving while no task is waiting is rejected with409 no waiting taskso the caller can retry once a pipeline is up. Non-matching paths return404, non-POST methods return405. - The listener is reference-counted by waiting tasks. When the last waiter resolves, aborts, or times out, the plugin closes the
Bun.serveinstance and frees the port.
Security
Always set secret_env in production. Without it, any caller that can reach the loopback port can fire the trigger. The plugin already binds to 127.0.0.1 by default; only change host when you deliberately need LAN/container reachability, and always pair a non-loopback bind with secret_env.
License
MIT
