Skip to content

Webhook Events is a capability that turns verified external events into DSH Sessions: an authenticated provider delivery (for example, a signed GitHub webhook) reaches a trusted programmatic rule, and a rule may create an ordinary root Session inside a Web Workspace to act on the event. Dispatch is process-local and fire-and-forget — there is no delivery database, queue, retry, deduplication, or Agent-completion state. The family is entirely user-composed: no shipped bundle wires it in.

PackageRolectx key
packages/webhook/webhookRule registry, callback lifecycle, and Workspace-backed Session creationctx.webhookRuntime
packages/webhook/webhook-githubSigned GitHub HTTP adapter (exact WebServer route, bounded body, HMAC-verified)consumes ctx.webhookRuntime + ctx.webServer

The split follows the seam pattern used across dsh capabilities: provider adapters authenticate and normalize deliveries, rules own arbitrary conditions and external calls, and the runtime owns callback lifetime plus Session creation. This is a different mechanism from packages/hooks/* (the Claude Code / Codex shell-hook bridges), which fire pre-existing hook configs during agent runs rather than starting new Sessions from external events.

The runtime: WebhookRuntime ​

ctx.webhookRuntime (packages/webhook/webhook/src/index.ts) is a Host-side registry for trusted programmatic rules with exactly two operations:

ts
register<K extends string>(rule: WebhookRule<K>): () => Promise<void>
dispatch<K extends string>(delivery: VerifiedWebhookDelivery<K>): void

WebhookRule<K> has a branded unique id, a provider kind, and run(delivery, signal). The callback may execute arbitrary trusted code and returns either null or one WebhookSessionRequest:

ts
interface WebhookRule<K extends string> {
  readonly id: WebhookRuleId
  readonly kind: K
  run(delivery: VerifiedWebhookDelivery<K>, signal: AbortSignal):
    Promise<WebhookSessionRequest | null> | WebhookSessionRequest | null
}

VerifiedWebhookDelivery<K> carries the provider kind, configured source, provider deliveryId, normalized lossless JSON event, and non-negative safe-integer receivedAt. The runtime validates, detaches, and freezes the complete value before dispatching it to more than one rule. deliveryId is provenance only — the runtime neither stores nor deduplicates it, so a repeated delivery runs the rules again.

dispatch() snapshots the matching rules, schedules each independently, and returns before any callback settles; throws and rejections are contained per rule. Registration is an effect whose awaitable disposer first hides the rule, then aborts and drains active callbacks — callbacks must observe the supplied signal, because same-process code that ignores cancellation cannot be forcibly stopped safely.

Session request ​

A non-null WebhookSessionRequest requires an absolute workspacePath, title, prompt, agentPreset, and permissionPreset. Optional model names an explicit provider/model route plus an output-token cap:

ts
interface WebhookSessionRequest {
  readonly workspacePath: string   // absolute; creates a root Session inside a Web Workspace
  readonly title: string
  readonly prompt: string          // exactly what the model sees — no private framing
  readonly agentPreset: string
  readonly permissionPreset: string
  readonly model?: { provider: string; model: string; maxTokens?: number }
}

An explicit route uses its adapter's reasoning default; omission snapshots the complete current deployment selection, including reasoning effort, until the first request records its durable header. The runtime validates presets before mutation, resolves or creates the canonical Workspace, creates an Agent with that Workspace path as SessionHeader.cwd, mounts the agent preset before publication, and durably attaches the Session before applying permissions, title, and prompt. Successful Agent.followup() is the webhook operation's commit point — the message uses source.kind: "webhook" with provider, source, delivery, and rule provenance. The runtime does not wait for idle, inspect the reply, or publish completion state; ordinary Agent and Session behavior owns everything afterward.

The GitHub adapter ​

@deepseek-ai/dsh-webhook-github registers one exact route (kind: 'exact') on an injected WebServer (packages/webhook/webhook-github/src/index.ts). Per request it resolves its credential reference, reads the raw body under a bounded maxBodyBytes ceiling, and verifies the X-Hub-Signature-256 HMAC before any JSON parsing — a body that never reaches the parser cannot be trusted:

CheckOrderFailure
POST onlybefore body read405 + Allow: POST
Content-Type: application/json (one optional UTF-8 charset)before body read415
Content-Length / streamed bytes ≤ maxBodyBytesduring body read413
X-Hub-Signature-256, x-github-delivery, x-github-event headers presentafter body read400
HMAC verification against the resolved secretbefore JSON parse401
Lossless JSON object payloadafter verification400
Dispatch to ctx.webhookRuntimelast503 if unavailable

On success the adapter answers 202 immediately after in-memory dispatch — it never waits for rules or Sessions to settle. The normalized event is a signed lossless-JSON object with { name: eventName, payload }, delivered as VerifiedWebhookDelivery<'github'>; this is provider-neutral delivery, and rules validate the event-specific fields they consume. A 503 is also returned when the webhook secret credential is unavailable.

The GitHub review guide (docs/user/guide/github-review.md) mounts this route on an isolated second WebServer so exposing webhook ingress never exposes the browser API. The shipped example overlay is apps/cli/config/examples/github-review/cordis.yml:

yaml
# Opt-in GitHub webhook overlay over the shipped Web composition. The second
# WebServer lives in an isolated realm so exposing it never exposes the UI API.
- insert:
    - id: webhook-runtime
      name: '@deepseek-ai/dsh-webhook'

    - id: github-ready-review-rule
      name: './github-ready-review-rule.mjs'
      config:
        source: primary-github
        repository: deepseek-harness/deepseek-harness
        workspacePath: !!js process.env.DSH_GITHUB_REVIEW_WORKSPACE ?? process.cwd()
        agentPreset: standard
        permissionPreset: read-only

    - id: github-webhook-ingress
      name: cordis:group
      group: true
      isolate:
        webServer: true
      config:
        - id: github-webhook-server
          name: '@deepseek-ai/dsh-host-webserver'
          config:
            host: '127.0.0.1'
            port: !!js Number(process.env.DSH_GITHUB_WEBHOOK_PORT ?? 3081)
        - id: github-webhook-adapter
          name: '@deepseek-ai/dsh-webhook-github'
          config:
            source: primary-github
            path: /github
            secretEnv: DSH_GITHUB_WEBHOOK_SECRET
            maxBodyBytes: 1048576

The rule module (github-ready-review-rule.mjs) is where the composition's logic lives: it inspects a delivery, decides whether to act, and returns a WebhookSessionRequest or null.

What fire-and-forget does not guarantee ​

These caveats are the contract's honest envelope — acceptance is not Agent success:

  • Process-local fire-and-forget only — a crash loses rule calls that have not admitted a prompt; there is no queue, replay, or retry.
  • No built-in deduplication — repeated provider deliveries may create repeated Sessions; rules that need idempotency own it.
  • No completion result — HTTP acceptance and rule settlement do not report Agent success, idle, or output.
  • Trusted callbacks must cooperate with cancellation — runtime teardown aborts and awaits them but cannot terminate arbitrary same-process code.
  • Workspace creation may outlive a failed Session attempt — an empty Workspace is retained because another concurrent caller may already use it.

Because none of this is wired into a shipped bundle, a deployment composes the runtime, adapter, and rule modules explicitly — including from a source checkout.

Packages ​

Package
@deepseek-ai/dsh-webhook
@deepseek-ai/dsh-webhook-github

Further reading ​

  • packages/webhook/README.md — the webhook family package map
  • packages/webhook/webhook/README.md — the rule interface and Session request contract
  • packages/webhook/webhook-github/README.md — the GitHub adapter surface
  • docs/subsystems/webhook.md — shared types and timing guarantees
  • docs/user/guide/github-review.md — the full composed GitHub review example
  • apps/cli/config/examples/github-review/cordis.yml — the shipped opt-in overlay