Skip to main content
What this is for: turn arbitrary HTTP events from external services (Gmail, GitHub, CI, custom) into agent messages or wake signals. Who it’s for: anyone wiring Comis into a broader automation pipeline. The webhook subsystem receives events from external services and routes them to agents or triggers daemon heartbeats. Comis provides two webhook endpoint types: a strict endpoint with required HMAC verification and a fixed payload schema, and mapped endpoints with path-based routing and a template engine for transforming arbitrary payloads into agent messages.
Comis-side webhooks vs. channel webhooks. This page describes the daemon’s own HTTP endpoints under /hooks/*. Some chat platforms (Telegram, Discord, Slack) also expose their own webhook callbacks. The Comis adapters for those platforms typically connect via long-poll or socket APIs by default; see Channels if you specifically need to expose your bot to a platform’s webhook callback.

Configuration

Top-Level Webhooks Config

Configure the webhook subsystem under the webhooks key in your config file.
Source: WebhooksConfigSchema in packages/core/src/config/schema-webhooks.ts

Webhook Mapping Config

Each entry in webhooks.mappings[] defines a routing rule.
Source: WebhookMappingConfigSchema in packages/core/src/config/schema-webhooks.ts

Match Conditions

The match object controls which incoming requests hit this mapping. If both path and source are provided, both must match (AND logic). If neither is provided, the mapping matches all requests (catch-all).
Source: WebhookMappingMatchSchema in packages/core/src/config/schema-webhooks.ts

Strict Webhook Endpoint

Route

POST /hooks/webhook The strict endpoint requires HMAC signature verification and validates the request body against a fixed schema. Use this when you control the sending service and can format payloads to the expected structure.

Request

The request body must conform to WebhookPayloadSchema:
POST /hooks/webhook
HMAC verification is required. The request must include a signature header (see HMAC Verification below).

Responses

Source: createWebhookEndpoint() in packages/gateway/src/webhook/webhook-endpoint.ts

Mapped Webhook Endpoint

Route

POST /hooks/:path The mapped endpoint accepts any JSON payload and routes it to the first matching webhook mapping’s action handler. HMAC verification is optional (applied only when webhooks.token is configured).

Path Routing

Incoming request paths are matched against the match.path field of each mapping in order. The first matching mapping wins. Normalization: Both the request path and the mapping path are normalized by stripping leading/trailing slashes and converting to lowercase.
Match resolution logic:
  1. If a mapping has no match conditions, it matches all requests (catch-all)
  2. If match.path is set, the normalized request path must equal it
  3. If match.source is set, the source field from the payload must equal it
  4. If both are set, both must match (AND logic)
  5. First match wins — mappings are evaluated in array order

Actions

"agent" (default): Renders the messageTemplate and sessionKey templates using the template engine, then invokes the agent with the rendered message and session key. "wake": Triggers a daemon heartbeat. The wakeMode controls timing:
  • "now" (default): Fire the heartbeat immediately
  • "next-heartbeat": Wait for the next scheduled heartbeat cycle

Responses

Source: createMappedWebhookEndpoint() in packages/gateway/src/webhook/webhook-endpoint.ts

HMAC Verification

Webhook signatures are verified using HMAC with constant-time comparison to prevent timing attacks.

Configuration

The HMAC middleware accepts the following configuration:
Source: HmacMiddlewareConfig in packages/gateway/src/webhook/hmac-verifier.ts

Signature Computation

The sender computes the signature as a hex-encoded HMAC of the raw request body:
Signature generation (Python example)
Signature generation (bash)

Verification Process

  1. Read the signature from the configured header (default: x-webhook-signature)
  2. If no signature header is present, return 401
  3. Compute the expected HMAC of the raw request body using the shared secret
  4. Compare using crypto.timingSafeEqual (constant-time to prevent timing attacks)
  5. If lengths differ, reject immediately (lengths cannot match for valid signatures)
  6. If signatures do not match, return 401

Timestamp Freshness

If a timestamp header is present (default: x-webhook-timestamp):
  1. Parse the header value as a Unix timestamp (seconds)
  2. Compare against the current time
  3. If the absolute difference exceeds maxAgeSec (default: 300 seconds), return 401
If requireTimestamp is true, requests without a timestamp header are rejected with 401. When false (default), missing timestamps are allowed — the HMAC signature alone provides tamper protection.
Source: verifyHmacSignature() and createHmacMiddleware() in packages/gateway/src/webhook/hmac-verifier.ts

Template Engine

Mapped webhook endpoints use a template engine to transform incoming payloads into agent messages and session keys. Templates use {{expression}} syntax.

Expression Types

Expressions without a recognized prefix (payload., headers., query.) are resolved against the payload object. This means {{repository.full_name}} is equivalent to {{payload.repository.full_name}}.

Context Object

The template engine receives a context object with the following properties:

Unresolved Expressions

Expressions that cannot be resolved (missing fields, null values) are replaced with an empty string. No error is thrown for unresolved expressions.
Source: resolveTemplateExpr() and renderTemplate() in packages/gateway/src/webhook/webhook-mapping.ts

Presets

Comis includes built-in webhook mapping presets for common services. Enable presets in your config:
Unknown preset names are silently ignored.

Gmail Preset

Receives Gmail push notifications and routes them as agent messages. The session key uses the first message ID for deduplication.

GitHub Preset

Receives GitHub webhook events and routes them as agent messages. The session key uses the delivery ID from the x-github-delivery header for deduplication.
Source: GMAIL_PRESET and GITHUB_PRESET in packages/gateway/src/webhook/webhook-presets.ts

Example Configuration

Full webhook config example

End-to-end: a GitHub webhook

A complete walkthrough that takes a real GitHub push event and turns it into an agent message.
1

Enable the webhook subsystem

Add the github preset and enable HMAC verification.
Reload config (comis config patch ... or restart) — the daemon now mounts POST /hooks/github.
2

Generate the shared secret

Use a 32+ character random string. Store it both in Comis (via comis secrets set WEBHOOK_SHARED_SECRET) and in GitHub’s webhook UI as the Secret field.
3

Configure the GitHub webhook

Repository → Settings → Webhooks → Add webhook:
  • Payload URL: https://your-host.example.com/hooks/github
  • Content type: application/json
  • Secret: the value from the previous step
  • SSL verification: enabled
  • Select the events you want (e.g. just push)
4

Verify the signature flow

Trigger a test event from GitHub’s webhook page. Comis will compute HMAC-SHA256 over the raw body using your secret and compare against the x-hub-signature-256 header. A successful 200 response carries {"received": true, "mapping": "github"}.Tail logs while testing:
5

Troubleshoot 401s

If GitHub shows a 401: re-check the secret matches on both sides, that the header name is x-hub-signature-256 (configurable via headerName), and that no proxy strips signature headers in front of the daemon.
The result: every matching GitHub event becomes an agent message keyed by hook:github:<delivery-id> so retries are deduplicated automatically by GitHub’s redelivery machinery.

Security Model

HMAC verification and security architecture

HTTP Gateway

Full gateway endpoint reference

Hot Reload

Config reloading and skill discovery

Defense in Depth

User-friendly security overview