Skip to content

JITSU-100: webhook destination optional request signing (HMAC / Ed25519)#1392

Merged
absorbb merged 1 commit into
newjitsufrom
feat/webhook-body-signing
Jul 10, 2026
Merged

JITSU-100: webhook destination optional request signing (HMAC / Ed25519)#1392
absorbb merged 1 commit into
newjitsufrom
feat/webhook-body-signing

Conversation

@vklimontovich

@vklimontovich vklimontovich commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Closes JITSU-100

What

Adds optional request signing to the Webhook destination so receivers can
verify events came from Jitsu and weren't tampered with. Signing is off by
default, so existing webhook destinations are unchanged.

Config — signatureMethod

  • none (default) — no signing.
  • hmac — symmetric HMAC-SHA256 with a shared secret (Stripe/GitHub style).
    Simple, but whoever can verify can also forge — use only for endpoints you
    fully trust.
  • ed25519 — asymmetric signature. Jitsu signs with a private key; the
    receiver verifies with the public key and cannot forge messages. Preferred
    when many or untrusted endpoints receive the webhook. The private key is
    accepted as PEM or a bare base64 body (whitespace-tolerant).

The console form shows only the fields relevant to the chosen method
(signatureSecret for hmac, private key for ed25519, header + replay options for
both).

Header scheme (same for both methods)

  • Jitsu-Signature — hex signature (HMAC-SHA256 digest, or Ed25519 signature)
  • Jitsu-Signature-Timestamp — unix seconds (when replay protection is on)

Signed content is <timestamp>.<body> with replay protection (default) or the
raw body without it. Signing uses Node's crypto (createHmac / sign), both
available in the function runtime.

Receiver verification (defaults: replay protection on)

HMAC

const crypto = require("crypto");
function verifyHmac(rawBody, signature, timestamp, secret, maxAgeSec = 300) {
  const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) return false;
  return Math.abs(Date.now() / 1000 - Number(timestamp)) <= maxAgeSec;
}

Ed25519 (endpoint holds only the public key)

const crypto = require("crypto");
function verifyEd25519(rawBody, signature, timestamp, publicKeyPem, maxAgeSec = 300) {
  const ok = crypto.verify(null, Buffer.from(`${timestamp}.${rawBody}`), publicKeyPem, Buffer.from(signature, "hex"));
  if (!ok) return false;
  return Math.abs(Date.now() / 1000 - Number(timestamp)) <= maxAgeSec;
}

Verify against the raw request body (before JSON parsing).

Tests

webhook-destination.test.ts uses MSW to intercept the real fetch (no
mocked fetch, matching #1388) and asserts the outbound signature:
HMAC with/without timestamp, Ed25519 verified against a generated public key, the
bare-base64 key form with surrounding whitespace, and that a malformed key fails
as a non-retryable error.

Robustness

  • A malformed key/secret throws NoRetryError (dropped, not retried forever)
    instead of being wrapped as a retryable error.

Notes / follow-ups

  • Batch mode is unsigned. Signing runs in the rotor function, which in batch
    mode only enqueues to Bulker — the actual POST happens in Bulker (Go), which
    has no signing. The signing UI now warns that signing applies to stream mode
    only. Signing batches would be a separate Bulker (Go) change.
  • Ed25519 keys are pasted by the user for now. A "Jitsu generates the pair and
    shows the public key" UX is a possible fast-follow.

Deferred (low severity, reviewed and intentionally not fixed here)

  • Ed25519 private key is re-parsed (createPrivateKey) per event — could memoize
    by key string if it ever shows on a hot path.
  • signatureHeader can still collide with a user-configured header or
    Content-Type (signature wins); the value is now validated as a legal header
    name but not checked against other header names.
  • Switching signing method leaves the previous secret/key in stored config
    (hidden, unused) — secret-retention hygiene.
  • The /api/destinations catalog endpoint doesn't surface the new placeholder
    field (non-functional; the editor uses the client-side path).
  • Pre-existing: user-header parsing uses header.split(":"), which truncates
    values containing : and keeps leading whitespace.

@vklimontovich vklimontovich added the deploy:console Auto-deploy console to beta when this PR merges (JITSU-68) label Jul 9, 2026
@vklimontovich vklimontovich changed the title feat(webhook): optional HMAC-SHA256 request signing JITSU-100: webhook destination optional HMAC-SHA256 request signing Jul 9, 2026
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Jul 9, 2026

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook-signing change set in libs/destination-functions plus the console destination schema wiring and lockfile updates. I focused on request-signing correctness (payload/timestamp signing, header emission, and no-secret behavior) and didn’t find actionable bugs or regressions in this PR diff.

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook-destination signing changes (function implementation, schema exposure, tests, and lockfile updates). I didn’t find correctness, security, or user-visible regression issues in the current PR diff.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Jul 9, 2026

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed webhook signing implementation and test coverage across destination runtime, metadata schema, and console credential UI wiring. I found one correctness issue around user-facing behavior documentation (inline).

Comment thread libs/destination-functions/src/meta.ts Outdated

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook signing changes in destination function runtime, destination schema metadata, and the new tests. The HMAC implementation and test coverage for stream mode look solid. I left one correctness concern around how signing is communicated/configured for batch mode, since the current execution path still bypasses the new signing headers there.

Comment thread libs/destination-functions/src/meta.ts Outdated
Comment thread webapps/console/lib/schema/destinations.tsx
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Jul 9, 2026

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook signing changes in destination runtime/config/UI wiring and the new unit tests. I found one correctness question around config validation (inline), and otherwise the signing flow looks coherent for stream-mode requests.

Comment thread libs/destination-functions/src/meta.ts
@vklimontovich vklimontovich force-pushed the feat/webhook-body-signing branch from 7312ab2 to c759249 Compare July 9, 2026 13:37
@vklimontovich vklimontovich changed the title JITSU-100: webhook destination optional HMAC-SHA256 request signing JITSU-100: webhook destination optional request signing (HMAC / Ed25519) Jul 9, 2026

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook signing implementation across runtime logic, destination schema, console field wiring, and the added tests. I found one actionable correctness/security gap: signing silently turns off when a signing method is selected but the corresponding key material is missing.

Comment thread libs/destination-functions/src/functions/webhook-destination.ts Outdated
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Jul 9, 2026

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook signing changes across destination runtime, schema/UI wiring, and new tests. I focused on signing correctness, configuration behavior, and user-visible copy. I found one additional correctness/UX issue noted inline.

Comment thread webapps/console/lib/schema/destinations.tsx Outdated

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the PR changes in webhook signing and related console credential UI wiring (schema, placeholders, and new destination-function tests). I did not find additional bugs beyond the existing open review threads on this PR.

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the 8-file diff for webhook signing support and related console form wiring/tests. I focused on runtime correctness and security behavior in , destination schema/UI changes, and the new signing tests.\n\nI did not find additional actionable issues beyond the already-open discussion threads on this PR.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Jul 9, 2026

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the 8-file diff for webhook signing support and related console form wiring/tests. I focused on runtime correctness and security behavior in webhook-destination.ts, destination schema/UI changes, and the new signing tests.

I did not find additional actionable issues beyond the already-open discussion threads on this PR.

Add optional signing to the webhook destination via a `signatureMethod`
choice: none (default), hmac, or ed25519.

- hmac: symmetric HMAC-SHA256 with a shared secret (Stripe/GitHub style).
- ed25519: asymmetric signature — Jitsu signs with a private key, the
  receiver verifies with the public key and cannot forge messages. The
  private key is accepted as PEM or a bare base64 body.

Both emit the hex signature in a configurable header (default
`Jitsu-Signature`, validated as a legal HTTP header name). Replay protection
is on by default: the unix-seconds timestamp is folded into the signed
payload (`<timestamp>.<body>`) and sent in a companion `<header>-Timestamp`
header; it can be turned off to sign the body only.

Misconfigurations fail as NoRetryError instead of retrying forever: a
malformed key/secret, or a method selected with no key.

Signing covers stream-mode delivery only; batch mode (Bulker) is unsigned,
warned in both the destination form and the connection editor.

The console form shows only the fields relevant to the selected method, and
gains a reusable `placeholder` prop for config fields.

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the PR diff () focusing on webhook signing behavior and the related console editor changes.\n\nI did not find additional correctness/security regressions beyond the issues already captured in existing open review threads.

@jitsu-code-review jitsu-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the webhook signing implementation across destination runtime, schema metadata, and console UI wiring. I also checked the updated tests and existing resolved review threads; I didn’t find additional correctness, security, or user-visible regression issues in the current diff.

@absorbb absorbb merged commit 2db0796 into newjitsu Jul 10, 2026
6 checks passed
@absorbb absorbb deleted the feat/webhook-body-signing branch July 10, 2026 08:09
@github-actions

Copy link
Copy Markdown
Contributor

🚀 deploy:console label detected — triggered a beta console deployment to jitsu-cloud-infra. Track it in the deploy runs (newest at top): https://github.com/jitsucom/jitsu-cloud-infra/actions/workflows/deploy.yaml?query=event%3Aworkflow_dispatch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy:console Auto-deploy console to beta when this PR merges (JITSU-68)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants