Research sample · Node.js 24

Reliable Node.js webhook and job processing

Research question

Give me an exact-runtime standards audit of this Node.js webhook and worker implementation. Find reliability, concurrency, cancellation, and modernization issues, prioritize production-impacting findings, and provide updated code techniques.

Topics covered: Node.js background jobs, webhook idempotency, serverless retries

Default research

Node.js 24 · 18,587 characters

Research complete

Question

Give me an exact-runtime standards audit of this Node.js webhook and worker implementation. Find reliability, concurrency, cancellation, and modernization issues, prioritize production-impacting findings, and provide updated code techniques.

Submitted code · node-webhook-worker.tsTypeScript
/**
 * Discovery example: reliable webhook ingestion and leased background work.
 * Declared runtime: Node.js 24.
 *
 * The store is intentionally expressed as a transactional interface so the
 * concurrency contract is visible without binding the example to one driver.
 */

import { createHmac, randomUUID, timingSafeEqual } from "node:crypto"
import { setTimeout as delay } from "node:timers/promises"

type Json = null | boolean | number | string | Json[] | { [key: string]: Json }
type JobState = "pending" | "running" | "retry_wait" | "succeeded" | "dead"

type WebhookEnvelope = {
  id: string
  type: string
  createdAt: string
  payload: Json
}

type JobRecord = {
  id: string
  tenantId: string
  operationKey: string
  eventId: string
  eventType: string
  payload: Json
  state: JobState
  attempts: number
  maxAttempts: number
  availableAt: Date
  leaseOwner: string | null
  leaseToken: string | null
View all 615 lines
/**
 * Discovery example: reliable webhook ingestion and leased background work.
 * Declared runtime: Node.js 24.
 *
 * The store is intentionally expressed as a transactional interface so the
 * concurrency contract is visible without binding the example to one driver.
 */

import { createHmac, randomUUID, timingSafeEqual } from "node:crypto"
import { setTimeout as delay } from "node:timers/promises"

type Json = null | boolean | number | string | Json[] | { [key: string]: Json }
type JobState = "pending" | "running" | "retry_wait" | "succeeded" | "dead"

type WebhookEnvelope = {
  id: string
  type: string
  createdAt: string
  payload: Json
}

type JobRecord = {
  id: string
  tenantId: string
  operationKey: string
  eventId: string
  eventType: string
  payload: Json
  state: JobState
  attempts: number
  maxAttempts: number
  availableAt: Date
  leaseOwner: string | null
  leaseToken: string | null
  leaseExpiresAt: Date | null
  lastError: SerializedFailure | null
  createdAt: Date
  updatedAt: Date
  completedAt: Date | null
}

type JobEvent = {
  id: string
  tenantId: string
  jobId: string
  kind: "created" | "claimed" | "lease_renewed" | "retry_scheduled" | "succeeded" | "dead"
  details: Record<string, Json>
  createdAt: Date
}

type SerializedFailure = {
  name: string
  message: string
  code: string
  retryable: boolean
  stack?: string
}

type Claim = {
  job: JobRecord
  owner: string
  token: string
}

type RetryPolicy = {
  maxAttempts: number
  baseDelayMs: number
  maxDelayMs: number
  leaseMs: number
  heartbeatMs: number
  attemptTimeoutMs: number
}

type Logger = {
  info(event: string, fields?: Record<string, Json>): void
  warn(event: string, fields?: Record<string, Json>): void
  error(event: string, fields?: Record<string, Json>): void
}

class AppError extends Error {
  constructor(
    message: string,
    readonly code: string,
    readonly retryable: boolean,
    readonly status = 500,
    options?: ErrorOptions,
  ) {
    super(message, options)
    this.name = "AppError"
  }
}

class SignatureError extends AppError {
  constructor(message = "Webhook signature verification failed.") {
    super(message, "INVALID_SIGNATURE", false, 401)
    this.name = "SignatureError"
  }
}

class LeaseLostError extends AppError {
  constructor(jobId: string) {
    super(`Lease ownership was lost for job ${jobId}.`, "LEASE_LOST", true, 409)
    this.name = "LeaseLostError"
  }
}

function serializeFailure(error: unknown): SerializedFailure {
  if (error instanceof AppError) {
    return {
      name: error.name,
      message: error.message,
      code: error.code,
      retryable: error.retryable,
      stack: error.stack,
    }
  }
  if (error instanceof Error) {
    return {
      name: error.name,
      message: error.message,
      code: "UNEXPECTED_ERROR",
      retryable: true,
      stack: error.stack,
    }
  }
  return {
    name: "UnknownFailure",
    message: String(error),
    code: "UNKNOWN_FAILURE",
    retryable: false,
  }
}

function jsonLogger(scope: string): Logger {
  const write = (level: string, event: string, fields: Record<string, Json> = {}) => {
    process.stdout.write(`${JSON.stringify({
      at: new Date().toISOString(),
      level,
      scope,
      event,
      ...fields,
    })}\n`)
  }
  return {
    info: (event, fields) => write("info", event, fields),
    warn: (event, fields) => write("warn", event, fields),
    error: (event, fields) => write("error", event, fields),
  }
}

function parseSignatureHeader(value: string | null): { timestamp: number; signatures: string[] } {
  if (!value) throw new SignatureError("The signature header is missing.")
  const fields = value.split(",").map((part) => part.trim().split("=", 2))
  const timestamp = Number(fields.find(([key]) => key === "t")?.[1])
  const signatures = fields.filter(([key]) => key === "v1").map(([, signature]) => signature)
  if (!Number.isSafeInteger(timestamp) || signatures.length === 0) {
    throw new SignatureError("The signature header is malformed.")
  }
  return { timestamp, signatures }
}

function equalHex(left: string, right: string): boolean {
  if (!/^[a-f0-9]+$/i.test(left) || !/^[a-f0-9]+$/i.test(right)) return false
  const leftBytes = Buffer.from(left, "hex")
  const rightBytes = Buffer.from(right, "hex")
  return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes)
}

function verifyWebhook(input: {
  body: Uint8Array
  signatureHeader: string | null
  secret: string
  now?: Date
  toleranceSeconds?: number
}): void {
  const { timestamp, signatures } = parseSignatureHeader(input.signatureHeader)
  const nowSeconds = Math.floor((input.now ?? new Date()).getTime() / 1_000)
  const tolerance = input.toleranceSeconds ?? 300
  if (Math.abs(nowSeconds - timestamp) > tolerance) {
    throw new SignatureError("The webhook timestamp is outside the allowed window.")
  }
  const signedPayload = Buffer.concat([
    Buffer.from(`${timestamp}.`, "utf8"),
    Buffer.from(input.body),
  ])
  const expected = createHmac("sha256", input.secret).update(signedPayload).digest("hex")
  if (!signatures.some((signature) => equalHex(signature, expected))) {
    throw new SignatureError()
  }
}

function parseEnvelope(body: Uint8Array): WebhookEnvelope {
  let value: unknown
  try {
    value = JSON.parse(Buffer.from(body).toString("utf8"))
  } catch (error) {
    throw new AppError("Webhook JSON is invalid.", "INVALID_JSON", false, 400, { cause: error })
  }
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new AppError("Webhook payload must be an object.", "INVALID_EVENT", false, 400)
  }
  const event = value as Record<string, unknown>
  if (
    typeof event.id !== "string" || event.id.length < 4 ||
    typeof event.type !== "string" || event.type.length < 3 ||
    typeof event.createdAt !== "string" || Number.isNaN(Date.parse(event.createdAt)) ||
    !("payload" in event)
  ) {
    throw new AppError("Webhook fields are invalid.", "INVALID_EVENT", false, 400)
  }
  return {
    id: event.id,
    type: event.type,
    createdAt: event.createdAt,
    payload: event.payload as Json,
  }
}

interface Transaction {
  findJobByOperationKey(tenantId: string, operationKey: string): Promise<JobRecord | null>
  insertJob(job: JobRecord): Promise<void>
  insertEvent(event: JobEvent): Promise<void>
}

interface JobStore {
  transaction<T>(operation: (tx: Transaction) => Promise<T>): Promise<T>
  claimNext(input: {
    tenantId: string
    owner: string
    leaseMs: number
    now: Date
  }): Promise<Claim | null>
  renewLease(input: {
    tenantId: string
    jobId: string
    owner: string
    token: string
    leaseMs: number
    now: Date
  }): Promise<boolean>
  complete(input: {
    tenantId: string
    jobId: string
    owner: string
    token: string
    now: Date
    result: Json
  }): Promise<boolean>
  fail(input: {
    tenantId: string
    jobId: string
    owner: string
    token: string
    now: Date
    failure: SerializedFailure
    nextAvailableAt: Date | null
  }): Promise<boolean>
  releaseExpiredLeases(input: { tenantId: string; now: Date; limit: number }): Promise<number>
}

function operationKey(tenantId: string, event: WebhookEnvelope): string {
  return `${tenantId}:webhook:${event.id}`
}

async function enqueueWebhook(
  store: JobStore,
  input: { tenantId: string; event: WebhookEnvelope; maxAttempts: number; now?: Date },
): Promise<{ job: JobRecord; duplicate: boolean }> {
  const now = input.now ?? new Date()
  const key = operationKey(input.tenantId, input.event)
  return await store.transaction(async (tx) => {
    const existing = await tx.findJobByOperationKey(input.tenantId, key)
    if (existing) return { job: existing, duplicate: true }
    const job: JobRecord = {
      id: randomUUID(),
      tenantId: input.tenantId,
      operationKey: key,
      eventId: input.event.id,
      eventType: input.event.type,
      payload: input.event.payload,
      state: "pending",
      attempts: 0,
      maxAttempts: input.maxAttempts,
      availableAt: now,
      leaseOwner: null,
      leaseToken: null,
      leaseExpiresAt: null,
      lastError: null,
      createdAt: now,
      updatedAt: now,
      completedAt: null,
    }
    await tx.insertJob(job)
    await tx.insertEvent({
      id: randomUUID(),
      tenantId: input.tenantId,
      jobId: job.id,
      kind: "created",
      details: { eventId: input.event.id, eventType: input.event.type },
      createdAt: now,
    })
    return { job, duplicate: false }
  })
}

type TenantResolver = (request: Request, event: WebhookEnvelope) => Promise<string>

function createWebhookHandler(dependencies: {
  secret: string
  store: JobStore
  resolveTenant: TenantResolver
  logger?: Logger
}) {
  const logger = dependencies.logger ?? jsonLogger("webhook")
  return async (request: Request): Promise<Response> => {
    const body = new Uint8Array(await request.arrayBuffer())
    try {
      verifyWebhook({
        body,
        signatureHeader: request.headers.get("webhook-signature"),
        secret: dependencies.secret,
      })
      const event = parseEnvelope(body)
      const tenantId = await dependencies.resolveTenant(request, event)
      const queued = await enqueueWebhook(dependencies.store, {
        tenantId,
        event,
        maxAttempts: 6,
      })
      logger.info("webhook.accepted", {
        tenantId,
        eventId: event.id,
        jobId: queued.job.id,
        duplicate: queued.duplicate,
      })
      return Response.json(
        { accepted: true, duplicate: queued.duplicate, jobId: queued.job.id },
        { status: queued.duplicate ? 200 : 202 },
      )
    } catch (error) {
      const failure = serializeFailure(error)
      logger.warn("webhook.rejected", { code: failure.code, message: failure.message })
      const status = error instanceof AppError ? error.status : 500
      return Response.json({ error: { code: failure.code, message: failure.message } }, { status })
    }
  }
}

function backoffDelay(policy: RetryPolicy, attempt: number): number {
  const exponential = Math.min(policy.baseDelayMs * 2 ** Math.max(0, attempt - 1), policy.maxDelayMs)
  return Math.round(exponential * (0.8 + Math.random() * 0.4))
}

function abortReason(signal: AbortSignal): Error {
  return signal.reason instanceof Error
    ? signal.reason
    : new DOMException("The operation was aborted.", "AbortError")
}

function composeAttemptSignal(parent: AbortSignal, timeoutMs: number): AbortSignal {
  return AbortSignal.any([parent, AbortSignal.timeout(timeoutMs)])
}

type JobProcessor = (job: JobRecord, context: { signal: AbortSignal; logger: Logger }) => Promise<Json>

class LeaseHeartbeat implements AsyncDisposable {
  readonly controller = new AbortController()
  private timer: ReturnType<typeof setInterval> | null = null
  private stopped = false

  constructor(
    private readonly store: JobStore,
    private readonly claim: Claim,
    private readonly policy: RetryPolicy,
    private readonly logger: Logger,
  ) {}

  start(parent: AbortSignal): AbortSignal {
    const combined = AbortSignal.any([parent, this.controller.signal])
    this.timer = setInterval(() => {
      void this.tick().catch((error) => {
        this.logger.error("job.heartbeat_failed", {
          jobId: this.claim.job.id,
          message: error instanceof Error ? error.message : String(error),
        })
        this.controller.abort(error)
      })
    }, this.policy.heartbeatMs)
    this.timer.unref()
    return combined
  }

  private async tick(): Promise<void> {
    if (this.stopped) return
    const renewed = await this.store.renewLease({
      tenantId: this.claim.job.tenantId,
      jobId: this.claim.job.id,
      owner: this.claim.owner,
      token: this.claim.token,
      leaseMs: this.policy.leaseMs,
      now: new Date(),
    })
    if (!renewed) throw new LeaseLostError(this.claim.job.id)
  }

  async [Symbol.asyncDispose](): Promise<void> {
    this.stopped = true
    if (this.timer) clearInterval(this.timer)
    this.timer = null
  }
}

class JobWorker {
  private readonly controller = new AbortController()
  private readonly running = new Set<Promise<void>>()
  private accepting = true

  constructor(
    private readonly tenantId: string,
    private readonly owner: string,
    private readonly store: JobStore,
    private readonly processor: JobProcessor,
    private readonly policy: RetryPolicy,
    private readonly concurrency: number,
    private readonly logger = jsonLogger("worker"),
  ) {
    if (!Number.isInteger(concurrency) || concurrency < 1) {
      throw new RangeError("Worker concurrency must be a positive integer.")
    }
    if (policy.heartbeatMs >= policy.leaseMs / 2) {
      throw new RangeError("Heartbeat interval must be less than half the lease duration.")
    }
  }

  async run(): Promise<void> {
    this.installSignalHandlers()
    await this.store.releaseExpiredLeases({ tenantId: this.tenantId, now: new Date(), limit: 100 })
    try {
      while (this.accepting && !this.controller.signal.aborted) {
        while (this.running.size < this.concurrency && this.accepting) {
          const claim = await this.store.claimNext({
            tenantId: this.tenantId,
            owner: this.owner,
            leaseMs: this.policy.leaseMs,
            now: new Date(),
          })
          if (!claim) break
          const task = this.execute(claim)
          this.running.add(task)
          void task.finally(() => this.running.delete(task))
        }
        if (this.running.size === 0) {
          await delay(250, undefined, { signal: this.controller.signal }).catch(() => undefined)
        } else if (this.running.size >= this.concurrency) {
          await Promise.race(this.running)
        } else {
          await delay(50, undefined, { signal: this.controller.signal }).catch(() => undefined)
        }
      }
    } finally {
      this.accepting = false
      await Promise.allSettled(this.running)
      this.removeSignalHandlers()
    }
  }

  stop(reason = new AppError("Worker shutdown requested.", "WORKER_STOPPING", true, 503)): void {
    if (!this.accepting) return
    this.accepting = false
    this.controller.abort(reason)
  }

  private readonly onSignal = () => this.stop()

  private installSignalHandlers(): void {
    process.once("SIGTERM", this.onSignal)
    process.once("SIGINT", this.onSignal)
  }

  private removeSignalHandlers(): void {
    process.off("SIGTERM", this.onSignal)
    process.off("SIGINT", this.onSignal)
  }

  private async execute(claim: Claim): Promise<void> {
    const heartbeat = new LeaseHeartbeat(this.store, claim, this.policy, this.logger)
    await using lease = heartbeat
    const attemptSignal = composeAttemptSignal(lease.start(this.controller.signal), this.policy.attemptTimeoutMs)
    this.logger.info("job.started", {
      jobId: claim.job.id,
      attempt: claim.job.attempts,
      owner: claim.owner,
    })
    try {
      const result = await this.processor(claim.job, { signal: attemptSignal, logger: this.logger })
      if (attemptSignal.aborted) throw abortReason(attemptSignal)
      const completed = await this.store.complete({
        tenantId: claim.job.tenantId,
        jobId: claim.job.id,
        owner: claim.owner,
        token: claim.token,
        now: new Date(),
        result,
      })
      if (!completed) throw new LeaseLostError(claim.job.id)
      this.logger.info("job.succeeded", { jobId: claim.job.id })
    } catch (error) {
      await this.recordFailure(claim, error)
    }
  }

  private async recordFailure(claim: Claim, error: unknown): Promise<void> {
    const failure = serializeFailure(error)
    const attemptsExhausted = claim.job.attempts >= claim.job.maxAttempts
    const retryable = failure.retryable && !attemptsExhausted
    const nextAvailableAt = retryable
      ? new Date(Date.now() + backoffDelay(this.policy, claim.job.attempts))
      : null
    const recorded = await this.store.fail({
      tenantId: claim.job.tenantId,
      jobId: claim.job.id,
      owner: claim.owner,
      token: claim.token,
      now: new Date(),
      failure,
      nextAvailableAt,
    })
    if (!recorded) {
      this.logger.warn("job.failure_not_recorded", { jobId: claim.job.id, code: failure.code })
      return
    }
    this.logger.warn(retryable ? "job.retry_scheduled" : "job.dead", {
      jobId: claim.job.id,
      code: failure.code,
      retryable,
      nextAvailableAt: nextAvailableAt?.toISOString() ?? null,
    })
  }
}

type Delivery = {
  destination: URL
  body: Json
  idempotencyKey: string
}

async function deliverWebhook(delivery: Delivery, signal: AbortSignal): Promise<Json> {
  const response = await fetch(delivery.destination, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "idempotency-key": delivery.idempotencyKey,
    },
    body: JSON.stringify(delivery.body),
    signal,
  })
  if (!response.ok) {
    const retryable = response.status === 408 || response.status === 429 || response.status >= 500
    throw new AppError(
      `Delivery returned HTTP ${response.status}.`,
      `DELIVERY_HTTP_${response.status}`,
      retryable,
      response.status,
    )
  }
  return { status: response.status, deliveredAt: new Date().toISOString() }
}

function createProcessor(destinationForTenant: (tenantId: string) => Promise<URL>): JobProcessor {
  return async (job, context) => {
    context.signal.throwIfAborted()
    const destination = await destinationForTenant(job.tenantId)
    context.signal.throwIfAborted()
    return await deliverWebhook({
      destination,
      body: {
        sourceEventId: job.eventId,
        sourceEventType: job.eventType,
        payload: job.payload,
      },
      idempotencyKey: `${job.operationKey}:delivery`,
    }, context.signal)
  }
}

export function startTenantWorker(input: {
  tenantId: string
  store: JobStore
  destinationForTenant(tenantId: string): Promise<URL>
}): JobWorker {
  const policy: RetryPolicy = {
    maxAttempts: 6,
    baseDelayMs: 1_000,
    maxDelayMs: 60_000,
    leaseMs: 30_000,
    heartbeatMs: 10_000,
    attemptTimeoutMs: 20_000,
  }
  return new JobWorker(
    input.tenantId,
    `worker-${process.pid}-${randomUUID()}`,
    input.store,
    createProcessor(input.destinationForTenant),
    policy,
    4,
  )
}

export function webhookRoute(input: {
  secret: string
  store: JobStore
  resolveTenant: TenantResolver
}): (request: Request) => Promise<Response> {
  return createWebhookHandler(input)
}

Result

TypeScript3 priority findings4 displayed sources

Good primitives, three ownership gaps under load

The worker uses current cancellation and resource-management APIs, but response streams, detached promises, and heartbeat renewals need explicit ownership before the implementation is dependable under sustained load.

Priority findings

03
  1. 01

    Release every Fetch response body

    Fix now

    The delivery path returns without consuming or cancelling the response body. Explicit cleanup protects connection reuse when response bodies are intentionally discarded.

    node-webhook-worker.ts:566

    return { status: response.status, deliveredAt: new Date().toISOString() }
    WEB-6
  2. 02

    Own the derived promise from finally

    Fix now

    A rejected task creates a rejected promise from finally that is never observed. Remove the task on both settlement paths and handle the execution failure separately.

    node-webhook-worker.ts:450

    void task.finally(() => this.running.delete(task))
    WEB-3
  3. 03

    Serialize lease heartbeats

    Fix next

    A native interval can start a second asynchronous renewal before the first finishes. An abortable async interval makes sequencing and shutdown explicit.

    node-webhook-worker.ts:381

    void this.tick().catch((error) => {
    WEB-2

Own every task rejection

Remove the task on either settlement path and observe its failure at the same boundary.

Modernized techniqueTypeScript
const task = this.execute(claim)
this.running.add(task)

void task.then(
  () => this.running.delete(task),
  (error) => {
    this.running.delete(task)
    this.logger.error("job.failed", {
      message: error instanceof Error ? error.message : String(error),
    })
  },
)

Authoritative sources

What this Node.js webhook reliability audit covers

This TypeScript example follows a signed webhook from raw-body verification through tenant resolution, idempotent job creation, leased execution, retries, and outbound delivery. It combines the failure modes engineers encounter when Node.js background jobs meet serverless webhook retries and process shutdown.

Hattrick reviews the implementation against its declared Node.js 24 runtime, then connects production-impacting findings to exact lines in the submitted code. The audit distinguishes modern primitives worth keeping from ownership gaps that can create unhandled promise rejections, overlapping lease renewals, or poor HTTP connection reuse.

Engineering questions answered

  • Can an unread Fetch response body reduce connection reuse in a Node.js worker?
  • Can a detached Promise.finally chain create an unhandled rejection?
  • How should lease heartbeats, cancellation, and retries be coordinated under load?

How Hattrick approaches the question

  1. 01

    Exact-runtime research

    Checks AbortSignal, Fetch, timers/promises, and explicit resource management against current Node.js 24 behavior.

  2. 02

    Code-grounded findings

    Links each displayed reliability claim to the submitted worker source and a precise file location.

  3. 03

    Production prioritization

    Raises response cleanup and promise ownership before lower-impact style or abstraction advice.

Practical takeaway

Reliable webhook workers need explicit ownership

Idempotency keys and leases are only part of a dependable delivery system. The worker must also own every promise, response stream, renewal loop, timeout, and cancellation path so failures remain observable and retries remain safe.