All Docs
FeaturesCSI Teachable Replacement AppUpdated March 15, 2026

Webhook Delivery Now Signs Payloads with HMAC-SHA256

Webhook Delivery Now Signs Payloads with HMAC-SHA256

Version: 1.0.47

Overview

Starting in v1.0.47, every outbound webhook delivery is cryptographically signed using HMAC-SHA256. The signature is included as an X-Webhook-Signature header, allowing receiving systems to verify that a delivery genuinely originated from the platform and that the payload has not been tampered with in transit.

Background

Previous versions of the webhook delivery system (src/inngest/functions/webhook-deliver.ts) posted payloads to registered endpoint URLs without attaching a signature. Although each webhook registration already stored a secret value in the webhooks table (src/db/schema.ts), that secret was never used during delivery. Third-party systems configured to reject unsigned or incorrectly signed webhook requests would silently drop all deliveries.

What Changed

In the post-webhook step of the delivery function:

  1. The serialised request body is hashed using HMAC-SHA256 with the webhook's stored secret (delivery.webhookSecret).
  2. The hex-encoded digest is attached as the X-Webhook-Signature header on the outbound POST request.
  3. No other delivery behaviour is changed — retries, timeouts, and payload structure remain the same.

Verifying Signatures on Your Endpoint

To validate an incoming webhook on your server, recompute the HMAC-SHA256 digest of the raw request body using the shared secret and compare it to the value in the X-Webhook-Signature header. Example in Node.js:

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, secret, signatureHeader) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)          // raw Buffer or string — do not parse JSON first
    .digest('hex');

  // Use a timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

Important: Always compute the HMAC over the raw, unparsed body bytes. Parsing the body as JSON before hashing may alter whitespace or key ordering and cause the comparison to fail.

Configuration

The shared secret is set once per webhook registration. There is no additional configuration required in v1.0.47 — the platform reads the existing secret column from the webhooks table automatically.

If you registered a webhook before v1.0.47 and did not set a secret at the time, consult the webhook management UI or API to rotate or add a secret. Deliveries for webhooks with an empty secret will send an empty X-Webhook-Signature header.

Security Recommendations

  • Always verify the signature on your receiving endpoint before processing the payload.
  • Use a timing-safe comparison (e.g. crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing-based attacks.
  • Treat your webhook secret as a credential — rotate it if it is ever exposed.
  • Consider rejecting requests that do not include an X-Webhook-Signature header entirely.