Skip to content

feat(node-sdk): add framework webhook receiver middleware (Express, Hono, Next.js) #22 - #50

Open
Adityakk9031 wants to merge 3 commits into
inthhq:mainfrom
Adityakk9031:issue-22
Open

feat(node-sdk): add framework webhook receiver middleware (Express, Hono, Next.js) #22#50
Adityakk9031 wants to merge 3 commits into
inthhq:mainfrom
Adityakk9031:issue-22

Conversation

@Adityakk9031

@Adityakk9031 Adityakk9031 commented Aug 6, 2026

Copy link
Copy Markdown

Summary

  • Exposed dsar.webhooks.receiver() on the SDK client instance (WebhooksApi)
  • Updated Express, Hono, and Next.js App Router webhook handlers (expressWebhookHandler, honoWebhookHandler,
    extWebhookHandler) to accept either a WebhookReceiver instance or WebhookReceiverOptions directly
  • Exported expressWebhookMiddleware, honoWebhookMiddleware, and
    extWebhookMiddleware aliases
  • Added comprehensive unit tests for all framework adapters and client integration

Closes #22


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Adityakk9031, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3bc2fbb0-12a4-4675-921a-9370baa97072

📥 Commits

Reviewing files that changed from the base of the PR and between 11679e5 and c43a664.

📒 Files selected for processing (8)
  • packages/node-sdk/src/endpoints/webhooks.ts
  • packages/node-sdk/src/webhooks/express.ts
  • packages/node-sdk/src/webhooks/hono.ts
  • packages/node-sdk/src/webhooks/next.ts
  • packages/node-sdk/src/webhooks/receiver.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a framework-neutral webhook receiver factory to the Node SDK.
    • Webhook handlers now accept receiver configuration directly, simplifying setup.
    • Added webhook handler and middleware exports for Express, Hono, and Next.js.
    • Added public request types for Express and Next.js integrations.
  • Tests

    • Added coverage for receiver creation and framework middleware handling of valid signed webhook requests.

Walkthrough

The Node SDK adds a public webhook receiver factory, receiver-option support for Express, Hono, and Next.js handlers, middleware aliases, public exports, and tests for verified requests.

Changes

Webhook receiver middleware

Layer / File(s) Summary
Receiver API and public exports
packages/node-sdk/src/endpoints/webhooks.ts, packages/node-sdk/src/webhooks.ts
WebhooksApi.receiver now creates webhook receivers. Framework handlers, middleware aliases, and request types are publicly exported.
Framework receiver-option integration
packages/node-sdk/src/webhooks/express.ts, packages/node-sdk/src/webhooks/hono.ts, packages/node-sdk/src/webhooks/next.ts
Each framework adapter accepts an existing WebhookReceiver or WebhookReceiverOptions. Each adapter exports a middleware alias for its handler.
Adapter and SDK validation
packages/node-sdk/test/client.test.ts, packages/node-sdk/test/webhooks/*, package.json
Tests cover receiver creation, option-based middleware construction, signature verification, and successful responses. unrun is added as a development dependency.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: kayleewilliams

Poem

A rabbit checks the signed event trail,
Through Express, Hono, Next without fail.
Receivers bloom from options bright,
Middleware answers with status right.
Hop, hop—the webhook path is clear!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The root package.json adds the unrelated unrun development dependency, which is not tied to the linked issue objectives. Remove the unrun dependency unless the pull request provides a clear requirement for its use in the webhook middleware changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Node SDK framework webhook middleware support for Express, Hono, and Next.js.
Description check ✅ Passed The description directly summarizes the webhook receiver, framework middleware aliases, and related tests.
Linked Issues check ✅ Passed The changes add all three framework adapters, receiver options support, middleware exports, client integration, and tests for each adapter.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai
coderabbitai Bot requested a review from KayleeWilliams August 6, 2026 12:03

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/node-sdk/src/webhooks/express.ts`:
- Around line 28-44: Resolve the webhook receiver once when each adapter is
created, rather than inside each request callback, so configured receiver state
and handlers persist across requests. Apply this to resolveReceiver and the
adapter construction in packages/node-sdk/src/webhooks/express.ts#L28-L44,
packages/node-sdk/src/webhooks/hono.ts#L6-L22, and
packages/node-sdk/src/webhooks/next.ts#L11-L27; establish a supported options
handler-registration contract or require preconfigured WebhookReceiver instances
for event dispatch. Add regression coverage at the highest stable adapter
boundary for each public adapter and assert the handler side effect for a
verified event.

In `@packages/node-sdk/test/webhooks/express.test.ts`:
- Around line 101-114: Update the ExpressWebhookRequest fixture passed to
middleware so its body uses Buffer.from(JSON.stringify(...)) instead of a
string, matching the raw-body representation produced by express.raw() during
signature verification.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b46e1b82-8318-4e32-aecb-28a4e638ff90

📥 Commits

Reviewing files that changed from the base of the PR and between 364dfa2 and 11679e5.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • package.json
  • packages/node-sdk/src/endpoints/webhooks.ts
  • packages/node-sdk/src/webhooks.ts
  • packages/node-sdk/src/webhooks/express.ts
  • packages/node-sdk/src/webhooks/hono.ts
  • packages/node-sdk/src/webhooks/next.ts
  • packages/node-sdk/test/client.test.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: pullfrog
🧰 Additional context used
📓 Path-based instructions (5)
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

**/package.json: Use Bun only, keep bun.lock canonical, and do not create npm, pnpm, or Yarn lock files.
Prefer root catalogs for versions shared across workspaces and keep peer ranges compatible with the exact development dependency used by an adapter.
Add a Changeset for user-visible package behavior, public API, runtime requirement, or dependency compatibility changes; keep unrelated package changes out of the same Changeset.

Files:

  • package.json
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Preserve unrelated worktree changes; do not reset, rewrite, or delete work not created for the task.
Do not amend, force-push, publish, or open a pull request unless explicitly asked by the user.
Before committing, inspect git diff --check, the staged file list, and generated or lockfile changes.

Files:

  • package.json
  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/src/webhooks/hono.ts
  • packages/node-sdk/test/client.test.ts
  • packages/node-sdk/src/webhooks/next.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
  • packages/node-sdk/src/webhooks.ts
  • packages/node-sdk/src/webhooks/express.ts
  • packages/node-sdk/src/endpoints/webhooks.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Bun-managed, ESM-only TypeScript; compile product code with TypeScript 7, not the @typescript/typescript6 alias.
Use Effect 4 and keep effect, @effect/platform-*, @effect/sql-*, and @effect/vitest on one matching release line.
Decode unknown at system boundaries with Effect Schema or a focused guard; do not cast request bodies, persisted JSON, or adapter payloads into domain types.
Every persistence operation that reads or writes tenant data must require TenantContext and run through withTenant.
Tenant identifiers must come from verified runtime context, not request payloads or untrusted adapter metadata.
Keep policy evaluation deterministic; represent new inputs in the explainability trace and cover them with golden or matrix tests.
Lifecycle mutations must preserve auditability, typed failures, and legal clock behavior.
Adapter failures must not silently corrupt request state or bypass lifecycle rules.
Public exports must use explicit package entrypoints; avoid new convenience barrel files while preserving intentional entrypoints and dsar umbrella exports.
Public failures must use established typed error classes, catalog codes, and response envelopes.
Keep runtime behavior, schemas, SDK types, CLI commands, OpenAPI, and documentation synchronized.
Import focused Effect modules such as effect/Effect and effect/Schema; root effect imports are lint-restricted except for type-only imports.
Define services with Context.Service and provide implementations through Layers; keep requirements visible in the Effect environment.
Prefer Effect.gen, Effect.fn, and typed combinators over nested promises; use Effect.runPromise only at runtime, adapter, or test boundaries.
Model expected failures in the error channel with descriptive tagged error types; do not use defects for routine validation or integration failures.

Files:

  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/src/webhooks/hono.ts
  • packages/node-sdk/test/client.test.ts
  • packages/node-sdk/src/webhooks/next.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
  • packages/node-sdk/src/webhooks.ts
  • packages/node-sdk/src/webhooks/express.ts
  • packages/node-sdk/src/endpoints/webhooks.ts
**/*.{test.ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use @effect/vitest for Effectful tests and provide Layers explicitly; prefer it.effect, shared layer(...) setup, and TestClock over manual runtimes or real-time sleeps.

Files:

  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/test/client.test.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{ts,tsx}: For defects, add a regression test at the highest stable boundary that could reasonably have caught the defect before implementing the fix.
For user-visible behavior, prefer acceptance paths through a public API, CLI, SDK, or adapter contract; assert responses and durable side effects rather than internal calls.
Prefer real repository-owned implementations; replace external providers or deliberately controlled effects such as time and randomness, but do not mock internal layers merely to simplify tests.
Call a test E2E only when it crosses real process, transport, runtime, and storage boundaries; injected fetch, in-memory repositories, and direct calls are integration or component tests.
Never commit .only, .skip, debug logging, or unexplained snapshot rewrites.

Files:

  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/test/client.test.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
🔇 Additional comments (12)
packages/node-sdk/test/client.test.ts (1)

318-329: LGTM!

packages/node-sdk/test/webhooks/express.test.ts (1)

4-4: LGTM!

packages/node-sdk/test/webhooks/hono.test.ts (1)

4-16: LGTM!

Also applies to: 43-65

packages/node-sdk/test/webhooks/next.test.ts (1)

3-3: LGTM!

Also applies to: 45-73

package.json (1)

85-86: LGTM!

packages/node-sdk/src/endpoints/webhooks.ts (2)

5-6: LGTM!

Also applies to: 223-223


154-159: 📐 Maintainability & Code Quality

Changeset is present.

packages/node-sdk/src/webhooks.ts (2)

35-36: LGTM!


2-4: 🎯 Functional Correctness

No change needed.

The package export map exposes ./webhooks/express, ./webhooks/hono, and ./webhooks/next with both types and import entries.

packages/node-sdk/src/webhooks/express.ts (1)

3-4: LGTM!

Also applies to: 53-54

packages/node-sdk/src/webhooks/hono.ts (1)

3-4: LGTM!

Also applies to: 30-32

packages/node-sdk/src/webhooks/next.ts (1)

3-9: LGTM!

Also applies to: 36-37

Comment thread packages/node-sdk/src/webhooks/express.ts Outdated
Comment thread packages/node-sdk/test/webhooks/express.test.ts

@pullfrog pullfrog 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.

Caution

The new WebhookReceiverOptions overload builds a throwaway receiver inside each request, so there is no way to ever register an .on() handler on it. Events are signature-verified, ACKed with 200, and silently discarded — and because the sender sees 200, it never retries.

Reviewed changes — full initial review of the single commit 11679e5 (11 files) against main.

  • sdk.webhooks.receiver()createWebhookReceiver is surfaced on WebhooksApi in packages/node-sdk/src/endpoints/webhooks.ts, ignoring ctx.
  • Options overload on the three adaptersexpressWebhookHandler, honoWebhookHandler, and nextWebhookHandler now accept WebhookReceiver | WebhookReceiverOptions through a locally duplicated resolveReceiver helper.
  • *WebhookMiddleware aliases — added in each adapter as identical bindings to the existing *WebhookHandler exports.
  • Neutral ./webhooks entrypoint widened — it now re-exports the Express, Hono, and Next adapters plus ExpressWebhookRequest and NextWebhookRequest.
  • Root dependencyunrun@^0.3.1 added to root package.json devDependencies and bun.lock.
  • Tests — one client test and one per-adapter test covering the options form and the alias exports.

Verified locally: bun run --cwd packages/node-sdk test passes (9 files, 51 tests). No changeset is needed — the pending .changeset/webhook-receiver-middleware.md from #32 is unreleased and already covers this area.

⚠️ No test exercises handler dispatch through the options form

The three new adapter tests assert only that verify was called and that the status is 200. Both of those hold on the empty-handler-map path, so they pass identically whether or not the receiver is rebuilt per request — they cannot fail on the dispatch bug above. A test that registers an .on("request_captured", …) handler and asserts it fires is what would pin the intended behavior.

Technical details
# Adapter tests cannot fail on the dispatch defect

## Affected sites
- `packages/node-sdk/test/webhooks/express.test.ts:93-120` — asserts `verify` called + status 200 only
- `packages/node-sdk/test/webhooks/hono.test.ts:44-64` — same
- `packages/node-sdk/test/webhooks/next.test.ts:46-72` — same

`dispatchEvent` in `packages/node-sdk/src/webhooks/receiver.ts:248-270` returns `successResult()`
both when the event type is unknown and when no handler is registered, so a 200 assertion proves
nothing about dispatch.

## Required outcome
- At least one adapter test must register an event handler through whatever shape the options
  overload settles on, send a verified `request_captured` body, and assert the handler ran with
  the decoded event.
- Add a regression test that the same handler still fires on a *second* request through the same
  handler instance — that is the assertion that would have caught the per-request rebuild.

ℹ️ New public surface is undocumented

sdk.webhooks.receiver() and the *WebhookMiddleware aliases are not mentioned anywhere under docs/. docs/reference/developer/sdk-and-runtime.mdx lists webhooks as a client namespace but documents none of its methods, and there is no webhook-adapter page at all. The gap predates this PR (the adapters landed undocumented in #32), but AGENTS.md's change-impact table lists docs as a required companion for public-export changes, so it is worth closing while the surface is still being shaped.

ℹ️ Nitpicks

  • resolveReceiver is copy-pasted verbatim into express.ts (new lines 28-33), hono.ts (new lines 6-11), and next.ts (new lines 11-16). If the overload survives, it belongs in receiver.ts next to createWebhookReceiver so the discrimination logic has one definition.
  • The as WebhookReceiverOptions cast in each copy exists only because the extra typeof … === "function" conjunct defeats TypeScript's in-operator narrowing on the false branch. Dropping the conjunct lets "handle" in receiverOrOptions narrow both branches on its own and removes the cast, which AGENTS.md asks you to avoid.
  • expressWebhookMiddleware / honoWebhookMiddleware / nextWebhookMiddleware are exact aliases of the *Handler exports. Two permanent public names for one binding is surface you cannot remove later without a breaking change — worth confirming the alias is what #22 actually asked for rather than a rename.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/node-sdk/src/webhooks/express.ts Outdated
Comment thread packages/node-sdk/src/webhooks.ts Outdated
Comment thread package.json
"typescript": "catalog:utils",
"ultracite": "7.9.4"
"ultracite": "7.9.4",
"unrun": "^0.3.1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

unrun is not referenced anywhere in the repo — grep -rn "unrun" matches only this line and bun.lock. It looks like it was picked up incidentally rather than needed by this feature; adding an unused root devDependency also widens the supply-chain surface the minimumReleaseAge gate in bunfig.toml is there to manage. Worth dropping from both files unless something here depends on it.

Suggested change
"unrun": "^0.3.1"
"ultracite": "7.9.4"

@pullfrog pullfrog 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.

ℹ️ The blocking event-loss defect is properly fixed. Two minor suggestions inline.

Reviewed changes — the delta from 11679e5 to fdc303c, which reworks how the options overload builds its receiver.

  • Receiver resolution hoisted to construction timeresolveReceiver now runs once in each adapter factory (express.ts:37, hono.ts:15, next.ts:20) instead of inside the per-request closure, so the receiver and its blank-secret check are both established when the handler is built.
  • Added WebhookReceiverOptions.handlers — a { [T in WebhookEventType]?: WebhookEventHandler<T> } map seeded into the closure-local handler map inside createWebhookReceiver, giving the options form a registration path that .on() could never reach.
  • Consolidated resolveReceiver into receiver.ts — one definition replacing the three verbatim copies, and the as WebhookReceiverOptions cast is gone now that plain "handle" in x narrows both branches.
  • Reverted the ./webhooks adapter re-exports — the neutral entrypoint no longer pulls express, hono, and next into every consumer's typecheck.
  • Strengthened the three adapter tests — each now registers a handler through handlers, asserts it fires with the decoded event, and repeats the request.

I traced the new seeding path for ways a caller-registered handler could still be dropped and found none: isWebhookEventType is backed by a Set over webhookEventTypes, which is compile-time exhaustiveness-checked against the backend's NotificationEventType; the mapped type turns a misspelled key into a TS error; Object.entries sees own enumerable keys only; and handlers followed by .on() is plain last-write-wins. bun run --cwd packages/node-sdk test passes (9 files, 51 tests).

The unrun thread on package.json:86 is still open — it remains unreferenced anywhere in the repo.

ℹ️ Nitpicks

  • The pending .changeset/webhook-receiver-middleware.md describes "HMAC verification, typed receiver dispatch, and Express, Hono, and Next.js adapter subpaths" but not sdk.webhooks.receiver() or WebhookReceiverOptions.handlers. Since it is unreleased, extending its body is cheaper than adding a second entry and keeps the release notes matching the shipped surface.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/node-sdk/test/webhooks/express.test.ts
Comment thread packages/node-sdk/src/endpoints/webhooks.ts Outdated

@pullfrog pullfrog 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.

ℹ️ No new issues in this delta. Both open threads from the previous review are properly closed; the only thing still outstanding is the pre-existing unrun thread.

Reviewed changes — the delta from fdc303c to c43a664, which closes out the two inline comments left on the previous review.

  • Refreshed the two stale @param options linesendpoints/webhooks.ts:157 and receiver.ts:279 now both read "Signing secret, optional verifier override, and optional initial event handlers", so handlers is discoverable from the type docs rather than only from the source.
  • Added a construction-time throw testtest/webhooks/express.test.ts:150-153 asserts expressWebhookMiddleware({ signingSecret: " " }) throws "Webhook signing secret is required." synchronously.

I checked that the new test actually pins the hoist rather than restating the existing smoke check: expressWebhookHandler calls resolveReceiver in the factory body before returning the async closure, and createWebhookReceiver throws at receiver.ts:285-287, so the call throws synchronously today. Move resolveReceiver back inside the request closure and the factory returns a function instead — the assertion fails. That is the discriminating signal the previous review asked for. bun run --cwd packages/node-sdk test passes (9 files, 52 tests).

The unrun thread on package.json:86 stays open — re-grepped at c43a664 and the dependency's only occurrence in the repo is still the package.json line that declares it. The undocumented public surface (sdk.webhooks.receiver(), the *WebhookMiddleware aliases, handlers) and the changeset wording nit from the previous review are also unchanged; neither is re-raised here.

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus𝕏

@Adityakk9031

Copy link
Copy Markdown
Author

@KayleeWilliams have a look

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[node-sdk] Webhook receiver middleware (Express, Hono, Next.js)

1 participant