Skip to content

@moxxy/plugin-webhooks lets an external system (any service that emits HTTP webhooks) wake the agent. The plugin is fully generic — no GitHub/Stripe wrappers — so the same setup works for a payment processor, a source forge, a CI runner, or your own internal service. The agent handles vendor specifics conversationally via webhook_setup_guide.

The 30-second flow ​

You:  set up a webhook for issue events from <my system>
Agent: [calls webhook_setup_guide — gets a 9-step script]
Agent: where will the events come from? (this just names the trigger)
You:  github-issues
Agent: do you already have a public URL or should I spawn one?
You:  spawn one
Agent: [calls webhook_tunnel_start → cloudflared, returns the URL]
Agent: how does the source sign requests? (bearer / hmac / none)
You:  hmac with X-Hub-Signature-256, prefix sha256=
Agent: do you already have the secret or should I generate one?
You:  generate one
Agent: which event types should fire vs. ignore?
You:  fire on issues.opened only
Agent: what should I do per delivery, and which tools should I have?
You:  triage and email me. allow Bash + web_fetch.
Agent: [calls webhook_create]
Agent: paste this URL + secret into the source's webhook config:
       URL: https://<random>.trycloudflare.com/webhook/01HX…
       Secret: a3f7…b921 (record now — won't be shown again)

The setup tool returns the full step-by-step script as data — your prompt just relays it. No vendor-specific code in moxxy.

Verification schemes ​

SchemeUse whenConfig
noneLocal-only testing. Anyone with the URL fires the trigger.{ type: 'none' }
bearerSource sends a static token in Authorization: Bearer ….{ type: 'bearer', secret }
hmac (plain)Source signs the body with HMAC. Header carries the digest.{ type: 'hmac', secret, signatureHeader, algorithm, prefix?, scheme: 'plain' }
hmac (stripe-style)Header is t=<ts>,v1=<sig>, HMAC input is <ts>.<body>.{ type: 'hmac', …, scheme: 'stripe', timestampToleranceSec }

Signature comparisons run constant-time. Stripe-style verifications also reject deliveries whose timestamp drifts past tolerance.

Filters ​

include (any-of) and exclude (drop-if-any) rules narrow which verified deliveries fire the prompt. Each rule reads ONE field:

  • source: 'header' — request header value (case-insensitive)
  • source: 'jsonPath' — dot-separated path in the JSON body (action, pull_request.user.login)

Compare with equals: [...] (any-of, string-coerced) or matches: "regex".

Example — only fire on opened issues, never on closed PRs:

ts
filters: {
  include: [
    { source: 'header', name: 'x-event', equals: ['issues'] },
    { source: 'jsonPath', path: 'action', equals: ['opened'] },
  ],
  exclude: [
    { source: 'jsonPath', path: 'pull_request.merged', equals: ['true'] },
  ],
}

Idempotency ​

Webhook providers retry. Set idempotencyHeader to a delivery-id header (X-Delivery-Id, X-Event-Id, etc.) and duplicate deliveries get dropped silently. The dedupe cache is in-memory (per process); a restart resets it, but that's rarely an issue in practice.

Outcomes ​

Every fired prompt writes a Markdown record to ~/.moxxy/inbox/webhooks/<timestamp>-<trigger-name>.md with the final assistant message (or the error). Plug an onFired callback into buildWebhooksPlugin to also pipe outcomes to Telegram or another channel.

Public URL ​

External providers can't reach localhost. Three paths:

sh
# Option 1 — free cloudflared tunnel, no signup
# (via the agent: webhook_tunnel_start)
brew install cloudflared             # macOS; Linux: github.com/cloudflare/cloudflared/releases

# Option 2 — bring your own URL (Tailscale Funnel, reverse proxy, …)
# (via the agent: webhook_set_public_url https://your-host.example)

# Option 3 — keep it local (verification: none) for testing

Running 24/7 ​

The webhook listener auto-starts whenever any moxxy session boots, because the plugin's onInit hook spins up the HTTP server. To keep that session alive across reboots:

sh
moxxy serve --background           # one process: every channel + scheduler + webhooks
# or, just webhooks + scheduler if you don't want channels:
moxxy serve --background --except telegram,http

See Running as a service for the full catalog.

Testing without a real source ​

sh
# Inside the TUI, ask the agent:
# "fire trigger gh-issues with body {\"action\":\"opened\"} and header x-event=issues"
# The agent calls webhook_test, which bypasses HTTP + verification but
# still evaluates filters and runs the prompt.

Caveats ​

  • Verification is non-optional in production. verification: 'none' means anyone on the internet who finds the URL fires your agent with whatever tools you allowed.
  • Quick tunnels are ephemeral. A cloudflared trycloudflare.com URL lives only as long as the process. For stable URLs, use a named cloudflared tunnel, Tailscale Funnel, or a deployed reverse proxy and call webhook_set_public_url.
  • Filters are best-effort. A malformed JSON body skips JSON-path rules silently (treated as no-match). If you need stricter filtering, reject in the prompt itself.

Open source · self-hosted · MIT