dsh-webhook-tester

September 12, 2026 · View on GitHub

DeepSeek Harness plugin: offline webhook fixtures and HMAC signature maths, exposed as three model-callable tools.

The plugin never binds a socket, never starts a listener, and never sends a request. It generates deterministic sample deliveries, verifies HMAC-SHA256 signatures over the exact bytes received, and parses captured raw HTTP request text back into its parts — so a webhook receiver can be exercised and debugged entirely from a transcript.

Install

npx -y @deepseek-ai/dsh plugin --profile web add @qingshanjiluo/dsh-webhook-tester

Tools

ToolArgumentsReturns
webhook_generate_sampleeventType (string, required), overrides (JSON object, optional)requestedType, eventType, known, wireEvent, targetUrl, payload, payloadJson (2-space), bodyToPost (compact), headers, curl, byteLength, warnings
webhook_verify_signaturepayload, secret, signatureHeader (all required), tolerance (number, optional)valid, scheme, reason
webhook_parse_requestrawHttp (string, required)method, path, httpVersion, headers, body, bodyJson, parseError, warnings

All three are pure and report isConcurrencySafe: true, so a model may fan them out in parallel.

webhook_generate_sample

Built-in shapes: push, pull_request (aliases pr, pull-request), issue (alias issues), ping, generic. Payloads are fixed (identical ids, SHAs, and timestamps on every call), so a sample can be committed as a fixture. overrides is deep-merged onto the payload — objects merge key by key, arrays and scalars replace the sample value. The returned curl command is POSIX shell text with backslash continuations and single-quoted arguments (' escaped as '\''), and it posts bodyToPost — the same string to sign and verify against.

Set signingSecret to have the sample ship a matching X-Hub-Signature-256 header. An unknown event name falls back to the generic envelope and says so in warnings.

webhook_verify_signature

Recognised header formats (scheme reports the one detected):

schemeHeader shapeSigned message
github-sha256sha256=<hex> (X-Hub-Signature-256)the payload
stripe-timestampedt=<unix-seconds>,v1=<hex> (several v1= allowed for key rotation)t=<unix-seconds> + . + payload
raw-sha256bare 64-character hex digestthe payload
unknownanything else, including sha1=rejected with a reason

A pasted header line (X-Hub-Signature-256: sha256=...) is accepted; a leading field name is stripped. Hex is compared case-insensitively over equal-length buffers with timingSafeEqual. tolerance bounds how far t= may sit from the current time in seconds and defaults to defaultToleranceSeconds; pass 0 to skip the freshness check. Pass payload byte-for-byte as it arrived — re-serialised JSON will not verify. Anything that fails (empty secret or header, non-hex digest, mixed schemes, stale timestamp, mismatch) returns valid: false plus a reason rather than throwing.

webhook_parse_request

Splits on the first blank line (\r\n\r\n or \n\n). The request line yields method, path (including query), and httpVersion without the HTTP/ prefix. Header names are lowercased, folded continuation lines are joined with a single space, repeated headers are joined with ", ". body is preserved exactly — no newline normalisation, no trimming — so it can be handed straight to webhook_verify_signature. bodyJson holds the parsed body when it is valid JSON, otherwise null and parseError explains why. warnings covers a missing header/body separator, a missing or unusual version token, malformed header lines, Content-Length drift, Transfer-Encoding: chunked (reported, not decoded), and a JSON Content-Type with an empty body.

Configuration

FieldTypeDefaultMeaning
sampleUrlstringhttp://127.0.0.1:3080/api/webhooks/githubTarget URL for generated curl commands and the ping sample's hook.config.url.
signingSecretstring''When non-empty, samples carry X-Hub-Signature-256 computed over bodyToPost.
defaultToleranceSecondsnumber300Freshness window for Stripe-style t= timestamps.

Development

npm install --no-audit --no-fund
npx tsc --noEmit
npm run build          # lib/index.js + lib/index.d.ts
npx vitest run
node scripts/load-smoke.mjs

License

MIT