Skip to content

feat(examples): add Next.js deletion webhook quickstart #21 - #51

Open
Adityakk9031 wants to merge 1 commit into
inthhq:mainfrom
Adityakk9031:issue-21
Open

feat(examples): add Next.js deletion webhook quickstart #21#51
Adityakk9031 wants to merge 1 commit into
inthhq:mainfrom
Adityakk9031:issue-21

Conversation

@Adityakk9031

@Adityakk9031 Adityakk9031 commented Aug 6, 2026

Copy link
Copy Markdown

Summary

Adds a standalone, 5-minute quickstart example in \examples/nextjs-deletion-webhook\ demonstrating how to receive outbound DSAR deletion/erasure webhooks using @dsar/node-sdk/webhooks/next.

Features

  • App Router Webhook Handler: \�pp/api/webhooks/dsar/route.ts\ verifying HMAC signatures using
    extWebhookMiddleware.
  • User Erasure Integration: Extracts subject email from verified webhook payloads and executes deletion in a demo user store.
  • Automated Smoke Test: \scripts/smoke.ts\ seeds user records, signs a DSAR webhook payload, invokes the route handler, and asserts user record deletion and HTTP 200 { ok: true }\ ACK.
  • Documentation: 5-step quickstart guide with Vercel one-click deploy instructions.

Closes #21


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

@pullfrog

pullfrog Bot commented Aug 6, 2026

Copy link
Copy Markdown

This run croaked 😵

The workflow encountered an error before any progress could be reported. Please check the link below for details.

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog𝕏

@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: 46 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: f6879e09-e92e-4d48-995a-6bdd85daf1f5

📥 Commits

Reviewing files that changed from the base of the PR and between 364dfa2 and 8a16531.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • examples/nextjs-deletion-webhook/.env.example
  • examples/nextjs-deletion-webhook/.gitignore
  • examples/nextjs-deletion-webhook/README.md
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts
  • examples/nextjs-deletion-webhook/lib/db.ts
  • examples/nextjs-deletion-webhook/package.json
  • examples/nextjs-deletion-webhook/scripts/smoke.ts
  • examples/nextjs-deletion-webhook/tsconfig.json
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a framework-neutral webhook receiver available through the Node SDK.
    • Webhook integrations now support direct configuration with signing secrets and event handlers.
    • Added middleware aliases for Express, Hono, and Next.js.
    • Added a complete Next.js example for securely processing deletion requests and removing matching demo users.
  • Documentation

    • Added setup, configuration, testing, and deployment guidance for the Next.js example.
  • Tests

    • Expanded coverage for signature verification, event handling, repeated requests, and deletion workflows.

Walkthrough

The SDK adds configurable webhook receiver creation and middleware aliases for Express, Hono, and Next.js. A Next.js example adds signed DSAR event handling, demo-user deletion, project setup, documentation, and a smoke test.

Changes

Webhook receiver and Next.js example

Layer / File(s) Summary
Receiver factory and public API
packages/node-sdk/src/webhooks/receiver.ts, packages/node-sdk/src/endpoints/webhooks.ts, packages/node-sdk/test/client.test.ts
WebhookReceiverOptions accepts initial event handlers. The SDK exposes receiver creation and resolution through WebhooksApi.receiver.
Framework middleware integration
packages/node-sdk/src/webhooks/*.ts, packages/node-sdk/test/webhooks/*
Express, Hono, and Next.js handlers accept receiver instances or options. Each adapter exports a middleware alias. Tests cover signature verification, event handling, repeated requests, and signing-secret validation.
Next.js deletion webhook example
examples/nextjs-deletion-webhook/*, package.json
The example adds a file-backed demo database, signed DSAR event handling, environment and TypeScript configuration, setup documentation, and a smoke test that verifies deletion.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SmokeTest
  participant NextDSARRoute
  participant WebhookReceiver
  participant DemoDatabase
  SmokeTest->>NextDSARRoute: POST signed DSAR payload
  NextDSARRoute->>WebhookReceiver: verify and dispatch event
  WebhookReceiver->>NextDSARRoute: return event data
  NextDSARRoute->>DemoDatabase: delete user by email
  NextDSARRoute-->>SmokeTest: return acknowledgement
Loading

Possibly related PRs

  • inthhq/dsar#50: Adds the webhook middleware and receiver APIs used by this Next.js example.

Suggested reviewers: kayleewilliams

Poem

A rabbit signs the webhook bright,
Then hops through routes to set things right.
The receiver checks each secret key,
The demo user is deleted free.
Tests thump twice, the logs agree—
A tidy burrow, webhook-ready.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The example covers the route, signature verification, acknowledgment, setup, smoke test, and deployment, but the demo store loads JSON instead of using SQLite as required by issue #21. Implement the demo user store with SQLite and make the smoke test verify deletion from the SQLite database.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the addition of the Next.js deletion webhook quickstart, which is the primary change.
Description check ✅ Passed The description directly covers the Next.js route, signature verification, deletion flow, smoke test, documentation, and deployment instructions.
Out of Scope Changes check ✅ Passed The SDK middleware, receiver updates, tests, dependency, and example files support the quickstart objectives and do not introduce unrelated changes.
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 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: 10

🤖 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 `@examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts`:
- Around line 5-6: Remove the shared fallback signing secret: in route.ts, make
DSAR_WEBHOOK_SECRET required and fail configuration when absent; in
.env.example, replace the known value with an empty generated-secret
placeholder; in scripts/smoke.ts, require the test environment secret without a
fallback.
- Around line 21-24: Update both event-handler logs in
examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts at lines 21-24
and 33-36 to remove the subject email, while retaining the event identifier and
deletion outcome in each message.

In `@examples/nextjs-deletion-webhook/lib/db.ts`:
- Around line 74-81: Update deleteByEmail in
examples/nextjs-deletion-webhook/lib/db.ts:74-81 to accept eventId, requestId,
and required lifecycle metadata, persist an audit record containing the event
identity, deletion outcome, and legal timestamp before acknowledging the
deletion, while preserving the boolean result. Update the DSAR webhook handler
in examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts:15-36 to pass
those values into deleteByEmail.
- Around line 26-48: Update the storage methods load() and save() to propagate
typed storage failures instead of silently resetting users or ignoring write
errors. Ensure the deletion webhook’s lifecycle mutation only acknowledges
success after save() completes durably, and returns the typed failure when load
or save fails while preserving existing legal clock behavior.
- Around line 26-45: Align the persistence implementation and environment
documentation: either replace the JSON logic in the database class methods load
and save with SQLite persistence, or explicitly retain JSON storage. If
retaining JSON, update examples/nextjs-deletion-webhook/.env.example lines 4-5
to use a JSON filename and describe it as a JSON database file; if implementing
SQLite, update the persistence methods and keep the existing SQLite
configuration documentation.

In `@examples/nextjs-deletion-webhook/README.md`:
- Around line 5-8: Update the README quickstart formatting around each step
heading and fenced code block: add the required blank lines before and after
every heading and fence, including the additional locations referenced in the
review, while preserving the existing commands and content.
- Around line 21-33: The request_captured handler in the documented
nextWebhookMiddleware example must validate event.payload.email before deletion
instead of casting it to string. Add a focused guard or Effect Schema decode for
the adapter payload, reject invalid or missing email values, and call
deleteDemoUserByEmail only with the validated string.

In `@examples/nextjs-deletion-webhook/scripts/smoke.ts`:
- Around line 71-74: Update the acknowledgement handling around response.json()
to treat the decoded payload as unknown and validate it with a focused guard
that accepts only an object whose ok property is exactly true. Remove the { ok:
boolean } cast, and retain the existing error behavior for all invalid response
shapes.

In `@packages/node-sdk/test/client.test.ts`:
- Around line 319-328: Expand the test for sdk.webhooks.receiver() to exercise
its public behavior: configure a verifier and request_captured handler, invoke
receiver.handle() with a signed payload, and assert the handler’s observable
effect plus the resulting { ok: true } acknowledgment. Replace the current
method-presence-only assertions while keeping the setup through the public SDK
API.

In `@packages/node-sdk/test/webhooks/next.test.ts`:
- Around line 75-89: Update the middleware response assertions in the test
around middleware so both res1 and res2 validate their parsed JSON bodies equal
{ ok: true }, in addition to the existing 200 status checks and handler
assertions.
🪄 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: b24c361d-154e-4242-adea-d8c3e320207f

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • examples/nextjs-deletion-webhook/.env.example
  • examples/nextjs-deletion-webhook/.gitignore
  • examples/nextjs-deletion-webhook/README.md
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts
  • examples/nextjs-deletion-webhook/lib/db.ts
  • examples/nextjs-deletion-webhook/package.json
  • examples/nextjs-deletion-webhook/scripts/smoke.ts
  • examples/nextjs-deletion-webhook/tsconfig.json
  • package.json
  • 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/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
  • examples/nextjs-deletion-webhook/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/client.test.ts
  • examples/nextjs-deletion-webhook/tsconfig.json
  • examples/nextjs-deletion-webhook/package.json
  • examples/nextjs-deletion-webhook/README.md
  • packages/node-sdk/src/webhooks/hono.ts
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts
  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/src/webhooks/express.ts
  • examples/nextjs-deletion-webhook/scripts/smoke.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/src/endpoints/webhooks.ts
  • packages/node-sdk/src/webhooks/receiver.ts
  • examples/nextjs-deletion-webhook/lib/db.ts
  • packages/node-sdk/test/webhooks/next.test.ts
  • packages/node-sdk/src/webhooks/next.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/client.test.ts
  • packages/node-sdk/src/webhooks/hono.ts
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts
  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/src/webhooks/express.ts
  • examples/nextjs-deletion-webhook/scripts/smoke.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/src/endpoints/webhooks.ts
  • packages/node-sdk/src/webhooks/receiver.ts
  • examples/nextjs-deletion-webhook/lib/db.ts
  • packages/node-sdk/test/webhooks/next.test.ts
  • packages/node-sdk/src/webhooks/next.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/client.test.ts
  • packages/node-sdk/test/webhooks/hono.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/client.test.ts
  • packages/node-sdk/test/webhooks/hono.test.ts
  • packages/node-sdk/test/webhooks/express.test.ts
  • packages/node-sdk/test/webhooks/next.test.ts
🪛 ast-grep (0.45.0)
examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts

[warning] 21-23: Avoid logging sensitive data
Context: console.log(
[DSAR Webhook] Processed request_captured for ${email} (deleted: ${deleted})
)
Note: [CWE-532] Insertion of Sensitive Information into Log File.

(log-sensitive-data-typescript)


[warning] 33-35: Avoid logging sensitive data
Context: console.log(
[DSAR Webhook] Processed request_fulfilled for ${email} (deleted: ${deleted})
)
Note: [CWE-532] Insertion of Sensitive Information into Log File.

(log-sensitive-data-typescript)

examples/nextjs-deletion-webhook/scripts/smoke.ts

[warning] 38-38: Avoid logging sensitive data
Context: console.log(--> Found active user ${initialUser.id} (${initialUser.email}))
Note: [CWE-532] Insertion of Sensitive Information into Log File.

(log-sensitive-data-typescript)

examples/nextjs-deletion-webhook/lib/db.ts

[warning] 28-28: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(this.filePath, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 40-44: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(
this.filePath,
JSON.stringify(list, null, 2),
"utf8"
)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🪛 dotenv-linter (4.0.0)
examples/nextjs-deletion-webhook/.env.example

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 5-5: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 markdownlint-cli2 (0.23.2)
examples/nextjs-deletion-webhook/README.md

[warning] 7-7: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 8-8: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 12-12: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 14-14: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 18-18: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 35-35: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 36-36: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 40-40: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 42-42: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🔇 Additional comments (11)
packages/node-sdk/src/webhooks/receiver.ts (1)

14-17: LGTM!

Also applies to: 279-305, 334-344

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

5-6: LGTM!

Also applies to: 154-159, 223-223

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

3-4: LGTM!

Also applies to: 31-52

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

3-4: LGTM!

Also applies to: 14-32

examples/nextjs-deletion-webhook/.gitignore (1)

1-6: LGTM!

package.json (1)

85-86: LGTM!

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

4-4: LGTM!

Also applies to: 93-154

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

3-27: LGTM!

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

4-16: LGTM!

Also applies to: 43-81

examples/nextjs-deletion-webhook/package.json (1)

1-27: LGTM!

examples/nextjs-deletion-webhook/tsconfig.json (1)

1-19: LGTM!

Comment on lines +5 to +6
const signingSecret =
process.env.DSAR_WEBHOOK_SECRET ?? "whsec_demo_secret_key_12345";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the shared known signing secret.

The route, sample environment file, and smoke test use the same public value. A deployment without explicit configuration accepts forged deletion webhooks.

  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts#L5-L6: fail configuration when DSAR_WEBHOOK_SECRET is absent.
  • examples/nextjs-deletion-webhook/.env.example#L1-L2: use an empty generated-secret placeholder.
  • examples/nextjs-deletion-webhook/scripts/smoke.ts#L4-L5: require the test environment secret instead of using a fallback.
📍 Affects 3 files
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts#L5-L6 (this comment)
  • examples/nextjs-deletion-webhook/.env.example#L1-L2
  • examples/nextjs-deletion-webhook/scripts/smoke.ts#L4-L5
🤖 Prompt for 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.

In `@examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts` around lines
5 - 6, Remove the shared fallback signing secret: in route.ts, make
DSAR_WEBHOOK_SECRET required and fail configuration when absent; in
.env.example, replace the known value with an empty generated-secret
placeholder; in scripts/smoke.ts, require the test environment secret without a
fallback.

Comment thread examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts Outdated
Comment thread examples/nextjs-deletion-webhook/lib/db.ts
Comment on lines +26 to +48
private load(): void {
if (fs.existsSync(this.filePath)) {
try {
const data = fs.readFileSync(this.filePath, "utf8");
const list: readonly DemoUser[] = JSON.parse(data);
this.users = new Map(list.map((user) => [user.email, user]));
} catch {
this.users = new Map();
}
}
}

private save(): void {
try {
const list = Array.from(this.users.values());
fs.writeFileSync(
this.filePath,
JSON.stringify(list, null, 2),
"utf8"
);
} catch {
// Ignore filesystem write errors in read-only serverless environments
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not hide storage failures.

load() converts a read or parse failure into an empty store. save() ignores write failures. A webhook can then acknowledge a deletion that exists only in memory.

Return a typed storage failure. Do not acknowledge the deletion until durable persistence succeeds. As per coding guidelines, “Lifecycle mutations must preserve auditability, typed failures, and legal clock behavior.”

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 28-28: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(this.filePath, "utf8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 40-44: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(
this.filePath,
JSON.stringify(list, null, 2),
"utf8"
)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for 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.

In `@examples/nextjs-deletion-webhook/lib/db.ts` around lines 26 - 48, Update the
storage methods load() and save() to propagate typed storage failures instead of
silently resetting users or ignoring write errors. Ensure the deletion webhook’s
lifecycle mutation only acknowledges success after save() completes durably, and
returns the typed failure when load or save fails while preserving existing
legal clock behavior.

Source: Coding guidelines

Comment on lines +74 to +81
/** Deletes a demo user record by email. */
deleteByEmail(email: string): boolean {
const existed = this.users.has(email);
if (existed) {
this.users.delete(email);
this.save();
}
return existed;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Create a durable audit contract for deletion.

The route discards DSAR event identity, and the database deletes a record without an audit entry. The completed webhook cannot be traced to a deletion operation.

  • examples/nextjs-deletion-webhook/lib/db.ts#L74-L81: persist an audit record with event identity, outcome, and legal timestamp before acknowledging deletion.
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts#L15-L36: pass eventId, requestId, and required lifecycle metadata to the deletion operation.
📍 Affects 2 files
  • examples/nextjs-deletion-webhook/lib/db.ts#L74-L81 (this comment)
  • examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts#L15-L36
🤖 Prompt for 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.

In `@examples/nextjs-deletion-webhook/lib/db.ts` around lines 74 - 81, Update
deleteByEmail in examples/nextjs-deletion-webhook/lib/db.ts:74-81 to accept
eventId, requestId, and required lifecycle metadata, persist an audit record
containing the event identity, deletion outcome, and legal timestamp before
acknowledging the deletion, while preserving the boolean result. Update the DSAR
webhook handler in
examples/nextjs-deletion-webhook/app/api/webhooks/dsar/route.ts:15-36 to pass
those values into deleteByEmail.

Source: Coding guidelines

Comment thread examples/nextjs-deletion-webhook/README.md
Comment on lines +21 to +33
```typescript
import { nextWebhookMiddleware } from "@dsar/node-sdk/webhooks/next";
import { deleteDemoUserByEmail } from "../../../../lib/db";

export const POST = nextWebhookMiddleware({
signingSecret: process.env.DSAR_WEBHOOK_SECRET!,
handlers: {
request_captured: (event) => {
deleteDemoUserByEmail(event.payload.email as string);
},
},
});
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the email before deletion.

event.payload.email as string does not validate the adapter payload. A malformed but signed event can pass undefined or a non-string value into deleteDemoUserByEmail.

Decode event.payload with a focused guard or Effect Schema. Reject invalid payloads before calling the deletion function. Update the copied route example to show that validation.

As per coding guidelines, “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.”

🤖 Prompt for 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.

In `@examples/nextjs-deletion-webhook/README.md` around lines 21 - 33, The
request_captured handler in the documented nextWebhookMiddleware example must
validate event.payload.email before deletion instead of casting it to string.
Add a focused guard or Effect Schema decode for the adapter payload, reject
invalid or missing email values, and call deleteDemoUserByEmail only with the
validated string.

Source: Coding guidelines

Comment on lines +71 to +74
const responseBody = (await response.json()) as { ok: boolean };
if (!responseBody.ok) {
throw new Error("Expected response body { ok: true }");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Decode the acknowledgement response.

The cast allows invalid truthy values, such as { "ok": "yes" }, to pass. Treat response.json() as unknown and use a focused guard that requires exactly { ok: true }.

As per coding guidelines, “Decode unknown at system boundaries with Effect Schema or a focused guard; do not cast … adapter payloads into domain types.”

🤖 Prompt for 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.

In `@examples/nextjs-deletion-webhook/scripts/smoke.ts` around lines 71 - 74,
Update the acknowledgement handling around response.json() to treat the decoded
payload as unknown and validate it with a focused guard that accepts only an
object whose ok property is exactly true. Remove the { ok: boolean } cast, and
retain the existing error behavior for all invalid response shapes.

Source: Coding guidelines

Comment thread packages/node-sdk/test/client.test.ts Outdated
Comment on lines +319 to +328
it("creates a webhook receiver from sdk.webhooks.receiver()", () => {
const sdk = createNodeSdk({
baseUrl: "http://localhost:3000/api/v1",
token: TEST_API_TOKEN,
});

const receiver = sdk.webhooks.receiver({ signingSecret: "test-secret" });
expect(receiver).toBeDefined();
expect(typeof receiver.handle).toBe("function");
expect(typeof receiver.on).toBe("function");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Exercise the receiver behavior through the public API.

This test only checks method presence. It does not verify that sdk.webhooks.receiver() transfers handlers, invokes verification, dispatches an event, or returns the acknowledgment result.

Create a receiver with a verifier and a request_captured handler. Call receiver.handle() with a signed payload. Assert the handler effect and { ok: true } response.

As per coding guidelines, “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.”

🤖 Prompt for 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.

In `@packages/node-sdk/test/client.test.ts` around lines 319 - 328, Expand the
test for sdk.webhooks.receiver() to exercise its public behavior: configure a
verifier and request_captured handler, invoke receiver.handle() with a signed
payload, and assert the handler’s observable effect plus the resulting { ok:
true } acknowledgment. Replace the current method-presence-only assertions while
keeping the setup through the public SDK API.

Source: Coding guidelines

Comment on lines +75 to +89
const res1 = await middleware(makeReq());
expect(verify).toHaveBeenCalledTimes(1);
expect(res1.status).toBe(200);
expect(capturedHandler).toHaveBeenCalledTimes(1);
expect(capturedHandler).toHaveBeenCalledWith(
expect.objectContaining({
eventId: "evt_1",
eventType: "request_captured",
requestId: "req_1",
})
);

const res2 = await middleware(makeReq());
expect(res2.status).toBe(200);
expect(capturedHandler).toHaveBeenCalledTimes(2);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assert the acknowledgment response body.

A 200 response can contain an incompatible payload. Assert await res1.json() and await res2.json() equal { ok: true }.

As per coding guidelines, “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.”

🤖 Prompt for 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.

In `@packages/node-sdk/test/webhooks/next.test.ts` around lines 75 - 89, Update
the middleware response assertions in the test around middleware so both res1
and res2 validate their parsed JSON bodies equal { ok: true }, in addition to
the existing 200 status checks and handler assertions.

Source: Coding guidelines

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

[examples] Next.js deletion webhook quickstart

1 participant