Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ By adding selected `.mdc` files to `.cursor/rules/`, you can use these rules dir

### Backend and Full-Stack

- [Anthropic Claude API (TypeScript, Next.js)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/anthropic-claude-api-typescript.mdc) - Claude API integration with streaming responses, tool use, model selection guide, and production-grade error handling with exponential backoff.
- [Cloudflare Workers (Hono, Angular)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/cloudflare-workers-hono-angular-saas-cursorrules-prompt-file.mdc) - Full-stack SaaS applications on Cloudflare Workers with Hono APIs, Angular frontends, typed RPC, D1/Neon, and production observability.
- [Convex Best Practices](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/convex-cursorrules-prompt-file.mdc) - Convex development with best practices.
- [Deno Integration](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/deno-integration-techniques-cursorrules-prompt-fil.mdc) - Deno development with integration techniques.
Expand Down Expand Up @@ -208,6 +209,7 @@ By adding selected `.mdc` files to `.cursor/rules/`, you can use these rules dir
- [Snowflake Cortex AI](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/snowflake-cortex-ai-cursorrules-prompt-file.mdc) - AI_COMPLETE, AI_CLASSIFY, AI_EXTRACT, Cortex Search, and RAG applications.
- [Snowflake Data Engineering](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/snowflake-data-engineering-cursorrules-prompt-file.mdc) - Snowflake SQL, data pipelines (Dynamic Tables, Streams, Tasks, Snowpipe), semi-structured data, Snowflake PostgreSQL, and cost optimization.
- [Snowflake Snowpark Python & dbt](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/snowflake-snowpark-dbt-cursorrules-prompt-file.mdc) - Snowpark Python (DataFrames, UDFs, stored procedures) and dbt with the Snowflake adapter.
- [Stripe (Next.js, TypeScript)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/stripe-integration-nextjs-typescript.mdc) - Secure checkout sessions, idempotent webhook verification with signature validation, subscription lifecycle handling, and customer portal.
- [TypeScript (Axios)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/typescript-axios-cursorrules-prompt-file.mdc) - TypeScript development with Axios integration.

### Testing
Expand Down Expand Up @@ -322,6 +324,7 @@ By adding selected `.mdc` files to `.cursor/rules/`, you can use these rules dir

- [CursorList](https://cursorlist.com)
- [CursorDirectory](https://cursor.directory/)
- [Cursor Rules & AI Coding Config Pack](https://moukie76.github.io/cursor-rules-free/) - 27 production-ready rules for 16 stacks: Next.js, React, Python FastAPI, Go, Stripe, Anthropic Claude, Tailwind, Docker, and more.

Copy link
Copy Markdown
Contributor

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

Correct the linked pack’s published count.

The README says “27 production-ready rules,” while the linked page currently advertises “25 production-ready rule files.” Update the README or the linked page so the catalog does not publish conflicting counts. (moukie76.github.io)

🤖 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 `@README.md` at line 327, Update the “Cursor Rules & AI Coding Config Pack”
entry in the README to use the linked page’s current published count of 25
production-ready rule files, keeping the existing link and stack description
unchanged.


## How to Use

Expand Down
115 changes: 115 additions & 0 deletions rules/anthropic-claude-api-typescript.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
description: Anthropic Claude API integration with streaming responses, tool use, and production-grade error handling for TypeScript and Next.js
globs: **/claude.ts, **/anthropic.ts, **/api/chat/route.ts, **/*.ts
alwaysApply: false
---

# Anthropic Claude API — TypeScript / Next.js

## Client Setup
```ts
import Anthropic from "@anthropic-ai/sdk"

// Singleton — reuse across requests
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
})
```

## Basic Message
```ts
const message = await anthropic.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
messages: [
{ role: "user", content: "Explain this code: ..." }
],
})

const text = message.content[0].type === "text" ? message.content[0].text : ""
```

## Streaming (Next.js Edge)
```ts
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk"

export const runtime = "edge"

export async function POST(req: Request) {
const { messages } = await req.json()

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })

const stream = anthropic.messages.stream({
model: "claude-opus-4-6",
max_tokens: 2048,
system: "You are a helpful assistant.",
messages,
})
Comment on lines +40 to +49

Copy link
Copy Markdown
Contributor

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

Validate and bound the incoming messages before forwarding them.

req.json() is passed directly to a paid upstream API with no schema, role/content, size, authentication, or rate-limit checks. A public route can therefore generate malformed requests or incur unbounded token spend. Add request validation and enforce payload/history limits before calling Claude.

🤖 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 `@rules/anthropic-claude-api-typescript.mdc` around lines 40 - 49, Add
validation immediately after req.json() and before constructing Anthropic or
calling messages.stream: require an authenticated, rate-limited request and
validate messages against a schema with permitted roles and content types.
Enforce maximum message count, content size, and any total payload/history
limits, returning an appropriate client error for invalid or excessive input;
only forward validated, bounded messages to Claude.


return new Response(stream.toReadableStream(), {
headers: { "Content-Type": "text/event-stream" },
})
Comment on lines +44 to +53

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Document mid-stream failure handling.

A try/catch around stream creation cannot catch errors emitted after the HTTP 200 response, and toReadableStream() forwards those failures to the consumer without an application-level error event or fallback. Anthropic documents this as a distinct streaming failure mode; add an SSE error/abort strategy and clarify that retries are only safe before output has been delivered. (platform.claude.com)

🤖 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 `@rules/anthropic-claude-api-typescript.mdc` around lines 44 - 53, Update the
streaming example around anthropic.messages.stream and stream.toReadableStream
to handle failures emitted after the HTTP 200 response by exposing an SSE error
event and aborting or closing the stream with an application-level fallback.
Document that retries are safe only before any output has been delivered, while
preserving the existing successful streaming response behavior.

}
```

## Tool Use
```ts
const response = await anthropic.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
tools: [{
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object",
properties: {
location: { type: "string", description: "City name" },
},
required: ["location"],
},
}],
messages: [{ role: "user", content: "What is the weather in Paris?" }],
})
```

## System Prompts
```ts
const system = `You are [role].

Your task: [specific task]

Rules:
- [rule 1]
- [rule 2]

Output format: [format]`
```

## Error Handling
```ts
try {
const message = await anthropic.messages.create(...)
} catch (err) {
if (err instanceof Anthropic.APIStatusError) {
if (err.status === 429) { /* rate limited — backoff and retry */ }
if (err.status === 529) { /* overloaded — retry with exponential backoff */ }
}
Comment on lines +95 to +98

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n '"`@anthropic-ai/sdk`"|APIStatusError|APIError' \
  --glob 'package.json' \
  --glob '*lock*' \
  --glob '!node_modules/**'

Repository: PatrickJS/awesome-cursorrules

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Candidate file lines 80-110:"
sed -n '80,110p' rules/anthropic-claude-api-typescript.mdc || true

echo
echo "Search for Anthropic error class mentions in tracked files:"
rg -n 'Anthropic\.(APIStatusError|APIError)' . --glob '!node_modules/**' || true

echo
echo "Lock/package references:"
for f in package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lockb bun.lock npm-shrinkwrap.json; do
  [ -f "$f" ] && echo "--- $f ---" && rg -n '"?`@anthropic-ai/sdk`"?|APIStatusError|APIError' "$f" || true
done

Repository: PatrickJS/awesome-cursorrules

Length of output: 1210


🌐 Web query:

Anthropic TypeScript SDK APIError APIStatusError @anthropic-ai/sdk``

💡 Result:

In the Anthropic TypeScript SDK (@anthropic-ai/sdk), APIError is the base class for errors returned by the API [1][2]. All errors in the SDK inherit from a base AnthropicError class [1]. While the Python SDK explicitly uses an APIStatusError class for non-success status codes [3][4], the TypeScript SDK primarily surfaces specific subclasses of APIError (such as BadRequestError, RateLimitError, and InternalServerError) to handle 4xx and 5xx responses [1][2][5]. Key Error Handling Details: 1. Hierarchy and Subclasses: When the API returns a non-success status code (4xx or 5xx), the SDK throws a specific subclass of APIError [5]. Common subclasses include [2][5]: - BadRequestError (400) - AuthenticationError (401) - PermissionDeniedError (403) - NotFoundError (404) - ConflictError (409) - UnprocessableEntityError (422) - RateLimitError (429) - InternalServerError (>=500) 2. Connection Errors: If the library cannot connect to the API or encounters a network issue, it throws an APIConnectionError or APIConnectionTimeoutError, which are also treated as APIError types [1][2][5]. 3. Inspecting Errors: You can catch these errors and inspect properties such as status, headers, and the raw error body [1][5]. The APIError instance includes the request-id header, which is essential for debugging and contacting support [1]. Example usage: const message = await client.messages.create({...}).catch((err) => { if (err instanceof Anthropic.APIError) { console.log(err.status); // e.g., 400 console.log(err.requestID); // Request ID for debugging } }); Note: If you encounter references to APIStatusError, please be aware that this is a core component of the Python SDK's error hierarchy [3][4] and is generally not exposed as a primary class in the TypeScript SDK's public API [2][5]. Always refer to the specific status-code-based subclasses when writing TypeScript error handling logic [5].

Citations:


🌐 Web query:

@anthropic-ai/sdk 1.4.0 APIStatusError APIError

💡 Result:

In the Anthropic TypeScript SDK (@anthropic-ai/sdk), APIError is the base class for all errors returned by the API [1]. While you mentioned APIStatusError, that specific class name is used in the Anthropic Python SDK [2][3][4] to handle 4xx and 5xx status codes, whereas the TypeScript SDK primarily uses the APIError class hierarchy to represent these errors [1][5]. In the TypeScript SDK: 1. APIError is the base class for errors that occur during API communication [1]. It includes properties such as status, message, headers, and the raw error body [6][1]. 2. Subclasses of APIError are used to represent specific HTTP status codes, such as BadRequestError (400), AuthenticationError (401), PermissionDeniedError (403), RateLimitError (429), and InternalServerError (>=500) [5][7]. 3. If you are catching errors, you should check against the base APIError class [5][7]. Because APIError is a common base, you can inspect the err.status or err.name properties on the caught error object to differentiate between specific failure types [5][7]. If you are seeing APIStatusError in documentation or code, ensure you are referencing the correct language-specific SDK documentation, as the exception naming conventions differ between the TypeScript and Python implementations [2][5]. For the TypeScript SDK, the authoritative reference for error handling is available in the official Claude documentation [7].

Citations:


🌐 Web query:

site:github.com/anthropics/anthropic-sdk-typescript APIStatusError APIError

💡 Result:

In the context of the Anthropic TypeScript/JavaScript SDK, APIError is a primary base class for errors thrown when the API returns a non-success status code (4xx or 5xx) [1]. APIStatusError is an internal method, makeStatusError, used by the client to generate these specific error instances based on the HTTP status code received [2]. Key points: - APIError: This is the parent class for all errors related to failed API requests [3][1]. When an API call returns a 4xx or 5xx status code, the SDK throws a subclass of APIError (such as BadRequestError, AuthenticationError, or RateLimitError) [1]. - makeStatusError (Internal): This is an internal client method (e.g., Anthropic.makeStatusError) that inspects the HTTP response and instantiates the appropriate APIError subclass [2]. - Error Handling: Developers should generally catch APIError to handle various API-related issues [1]. For example: const client = new Anthropic; try { await client.messages.create(...); } catch (err) { if (err instanceof Anthropic.APIError) { console.log(err.status); // HTTP status code (e.g., 400, 401) console.log(err.name); // Specific error name (e.g., AuthenticationError) } } [1]

Citations:


🌐 Web query:

platform.claude.com TypeScript SDK APIError APIStatusError

💡 Result:

In the Anthropic TypeScript SDK (@anthropic-ai/sdk), APIError is the base class for all errors returned by the API [1][2]. When the SDK encounters a non-success HTTP status code (4xx or 5xx) or a connection issue, it throws a specific subclass of APIError [1][3]. APIStatusError is not a standard top-level class exported in the TypeScript SDK's public API documentation, unlike in the Python SDK where it serves as a base for status-code-related exceptions [4]. In the TypeScript SDK, errors are generally categorized into APIError and its subclasses [1][5]. Key error subclasses include: - BadRequestError (400) [1][3] - AuthenticationError (401) [1][3] - PermissionDeniedError (403) [1][3] - NotFoundError (404) [1][3] - ConflictError (409) [1][3] - UnprocessableEntityError (422) [1][3] - RateLimitError (429) [1][3] - InternalServerError (>=500) [1][3] - APIConnectionError (N/A, typically network-related) [1][3] These errors allow you to catch and handle specific failure conditions effectively [1][6]. The APIError objects provide access to metadata such as the HTTP status code (err.status), the error name (err.name), headers, and the request ID, which is essential for debugging [1][3][7]. The SDK also supports automatic retries for certain error types (e.g., 408, 429, and 5xx errors), which can be configured via the maxRetries option [3].

Citations:


🌐 Web query:

site:platform.claude.com/docs/en/cli-sdks-libraries/sdks/typescript APIError APIStatusError

💡 Result:

APIError is a base exception class used by certain software development kits (SDKs), such as the Anthropic TypeScript SDK, to handle errors that occur when interacting with an API [1]. When an SDK is unable to successfully connect to an API or receives a non-success HTTP status code (typically 4xx or 5xx ranges), it throws a subclass of APIError [1]. These subclasses allow developers to inspect details about the failure, such as the status code, the specific error type (e.g., BadRequestError, AuthenticationError, or RateLimitError), and response headers [1]. While APIError is a standard convention in several client libraries for representing API-related failures, APIStatusError is not a widely documented or standard term across major SDKs based on available information. It is possible that APIStatusError refers to a library-specific error naming convention or a custom implementation in a codebase not covered by general documentation [1]. Example usage for handling APIError in TypeScript: const message = await client.messages.create({... }).catch(async (err) => { if (err instanceof Anthropic.APIError) { console.log(err.status); console.log(err.name); } else { throw err; } });

Citations:


Use the TypeScript SDK’s APIError class.

Anthropic.APIStatusError is a Python SDK error name; the documented TypeScript SDK exposes Anthropic.APIError and its status-code subclasses. This copied snippet can fail TypeScript checking before handling 429/529 responses.

Proposed fix
-  if (err instanceof Anthropic.APIStatusError) {
+  if (err instanceof Anthropic.APIError) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (err instanceof Anthropic.APIStatusError) {
if (err.status === 429) { /* rate limited — backoff and retry */ }
if (err.status === 529) { /* overloaded — retry with exponential backoff */ }
}
if (err instanceof Anthropic.APIError) {
if (err.status === 429) { /* rate limited — backoff and retry */ }
if (err.status === 529) { /* overloaded — retry with exponential backoff */ }
}
🤖 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 `@rules/anthropic-claude-api-typescript.mdc` around lines 95 - 98, Replace the
invalid Anthropic.APIStatusError check with the TypeScript SDK’s
Anthropic.APIError (or its documented status-code subclasses) in the 429/529
handling logic, preserving the existing rate-limit and overload retry behavior.

throw err
}
```

## Model Selection
- claude-haiku-4-5-20251001: fast and cheap, good for classification, short tasks
- claude-sonnet-4-6: balanced speed/intelligence for most tasks
- claude-opus-4-6: maximum intelligence for complex reasoning
Comment on lines +103 to +106

Copy link
Copy Markdown
Contributor

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

Refresh the model-selection recommendations.

These IDs remain valid, but the descriptions are stale for the current model lineup: Anthropic now lists Claude Fable 5 as the highest-capability model, with Opus 5 and Sonnet 5 as current alternatives. Either update this table or label the 4.6 models as compatibility choices. (platform.claude.com)

🤖 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 `@rules/anthropic-claude-api-typescript.mdc` around lines 103 - 106, Update the
Model Selection table to reflect Anthropic’s current lineup by adding Claude
Fable 5 as the highest-capability recommendation and Opus 5 and Sonnet 5 as
current alternatives, or explicitly label the existing claude-opus-4-6 and
claude-sonnet-4-6 entries as compatibility choices while preserving their valid
IDs.


## Rules
- Always set max_tokens explicitly — the default may truncate responses

Copy link
Copy Markdown
Contributor

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

Correct the max_tokens explanation.

max_tokens is the requested output ceiling, not a default that may silently truncate responses. The API reference describes it as the maximum number of generated tokens; explain that callers must choose an appropriate cap and handle stop_reason: "max_tokens" when relevant. (platform.claude.com)

🤖 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 `@rules/anthropic-claude-api-typescript.mdc` at line 109, Update the max_tokens
guidance in the Anthropic Claude API rules to describe it as the caller-selected
maximum output-token ceiling, not a default that silently truncates responses.
Instruct callers to choose an appropriate limit and handle stop_reason:
"max_tokens" when the response reaches that cap.

- Use a singleton client instance, not a new one per request
- Handle 429 (rate limit) and 529 (overloaded) with exponential backoff
- Monitor usage.input_tokens + usage.output_tokens per request for cost control
- Trim conversation history before sending — avoid token bloat on long sessions
- Never hardcode API keys — use process.env.ANTHROPIC_API_KEY
- Do not use deprecated claude-1 or claude-2 model IDs
109 changes: 109 additions & 0 deletions rules/stripe-integration-nextjs-typescript.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
description: Secure Stripe integration with typed checkout sessions, idempotent webhook verification, and subscription lifecycle handling in Next.js TypeScript
globs: **/stripe.ts, **/webhook/route.ts, **/checkout/route.ts, **/*.ts
alwaysApply: false
---

# Stripe Integration — Next.js TypeScript

## Client Setup
```ts
// lib/stripe.ts
import Stripe from "stripe"

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
Comment on lines +3 to +14

Copy link
Copy Markdown
Contributor

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

Narrow the rule scope and enforce a server-only Stripe client.

**/*.ts applies this rule to client components and unrelated browser code, while lib/stripe.ts reads STRIPE_SECRET_KEY without an explicit server-only boundary. Restrict the glob to server modules/routes and add import "server-only" so the secret-bearing client cannot be imported into client code. This matches the repository’s Stripe secret-handling invariant from rules/nextjs15-supabase-cursorrules-prompt-file.mdc.

🤖 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 `@rules/stripe-integration-nextjs-typescript.mdc` around lines 3 - 14, Restrict
the rule’s globs to Stripe server modules and route handlers, removing the broad
**/*.ts pattern that covers client and unrelated browser code. In the Stripe
client setup for lib/stripe.ts, add the server-only import before creating the
client so modules using STRIPE_SECRET_KEY cannot be imported into client code.

apiVersion: "2024-12-18.acacia",
typescript: true,
})
```

## Checkout Session
```ts
export async function createCheckoutSession({
priceId,
userId,
successUrl,
cancelUrl,
}: {
priceId: string
userId: string
successUrl: string
cancelUrl: string
}) {
return stripe.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card"],
line_items: [{ price: priceId, quantity: 1 }],
success_url: successUrl,
cancel_url: cancelUrl,
metadata: { userId },
subscription_data: {
metadata: { userId },
},
})
Comment on lines +22 to +43

Copy link
Copy Markdown
Contributor

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

Validate caller-controlled Stripe identifiers and URLs on the server.

The checkout and portal helpers accept arbitrary priceId, userId, customerId, and redirect URLs. If these originate from request data, callers can select unintended prices, attach metadata to another account, or redirect users to attacker-controlled origins. Derive identities from authentication, map plan names to server-owned prices, and allowlist same-origin redirect URLs. Also clarify that webhook verification authenticates the Stripe event; it does not replace server-side price or entitlement validation. (docs.stripe.com)

  • rules/stripe-integration-nextjs-typescript.mdc#L22-L43: validate/authenticate checkout inputs before creating the session.
  • rules/stripe-integration-nextjs-typescript.mdc#L86-L90: derive and authorize customerId, and validate returnUrl.
  • rules/stripe-integration-nextjs-typescript.mdc#L102-L107: document these server-side authorization and allowlisting requirements.
📍 Affects 1 file
  • rules/stripe-integration-nextjs-typescript.mdc#L22-L43 (this comment)
  • rules/stripe-integration-nextjs-typescript.mdc#L86-L90
  • rules/stripe-integration-nextjs-typescript.mdc#L102-L107
🤖 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 `@rules/stripe-integration-nextjs-typescript.mdc` around lines 22 - 43, Update
createCheckoutSession to authenticate the caller, derive and authorize userId
server-side, map plan selections to server-owned Stripe prices instead of
trusting arbitrary priceId values, and allowlist same-origin success and cancel
URLs. At rules/stripe-integration-nextjs-typescript.mdc lines 86-90, apply
equivalent customerId derivation/authorization and returnUrl validation to the
portal helper; at lines 102-107, document that webhook verification
authenticates Stripe events but does not replace server-side price or
entitlement validation.

}
```

## Webhooks — Critical Patterns
```ts
// app/api/stripe/webhook/route.ts
import { headers } from "next/headers"

export async function POST(req: Request) {
const body = await req.text()
const signature = headers().get("stripe-signature")!
Comment on lines +50 to +54

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- candidate file excerpt ---\n'
sed -n '1,120p' rules/stripe-integration-nextjs-typescript.mdc || true

printf '\n--- related files mentioning next/headers ---\n'
rg -n 'from ["\x27]next/headers["\x27]|headers\(\)|stripe-signature' -S . || true

printf '\n--- package/config hints ---\n'
for f in package.json tsconfig.json next.config.js next.config.mjs app.config.js app.config.mjs; do
  [ -f "$f" ] && { echo "### $f"; sed -n '1,220p' "$f"; }
done

Repository: PatrickJS/awesome-cursorrules

Length of output: 258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate file excerpt ---'
sed -n '1,120p' rules/stripe-integration-nextjs-typescript.mdc || true

printf '%s\n' ''
printf '%s\n' '--- related files mentioning next/headers ---'
rg -n 'from ["'"'"']next/headers["'"'"']|headers\(\)|stripe-signature' -S . || true

printf '%s\n' ''
printf '%s\n' '--- package/config hints ---'
for f in package.json tsconfig.json next.config.js next.config.mjs app.config.js app.config.mjs; do
  [ -f "$f" ] && { echo "### $f"; sed -n '1,220p' "$f"; }
done

Repository: PatrickJS/awesome-cursorrules

Length of output: 4586


🌐 Web query:

Next.js next/headers headers() function async returns Promise RequestHeaders documentation

💡 Result:

In Next.js 15 and later, the headers() function from next/headers is an asynchronous function that returns a Promise [1][2]. This change was introduced as a breaking update to better support streaming and request-time data access [3][4]. Key details regarding this change: How to Use You must now use async/await to access the headers object [1][5]: import { headers } from 'next/headers'; export default async function Page { const headersList = await headers; const userAgent = headersList.get('user-agent'); //... } In Client Components or synchronous contexts where you cannot use async/await, you can use React's use() function to unwrap the Promise [1][3]: 'use client'; import { use } from 'react'; import { headers } from 'next/headers'; function MyComponent { const headersList = use(headers); const userAgent = headersList.get('user-agent'); //... } Migration and Compatibility Next.js provides a codemod, next-async-request-api, to automatically migrate codebases to the new asynchronous pattern [3]. While synchronous access remains temporarily supported for backward compatibility, it is deprecated, will trigger warnings in development, and may be removed in future major versions [1][6][2]. Important Considerations - Dynamic Rendering: Like previous versions, using headers() opts the route segment into dynamic rendering [7][5]. - Web Standard: The function returns a read-only Web Headers object, consistent with previous versions [1][7]. - Third-Party Libraries: If a library you use relies on the synchronous headers() API, it may throw errors. You should check if the library has released a version compatible with the asynchronous requirements of newer Next.js versions [2].

Citations:


Await headers() before reading the signature.

This rule targets this code path, and Next.js’ App Router headers() API is async; synchronous access is deprecated compatibility behavior. Use const requestHeaders = await headers() before calling .get("stripe-signature").

🤖 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 `@rules/stripe-integration-nextjs-typescript.mdc` around lines 50 - 54, Update
the POST function to await headers() and store the resolved headers in
requestHeaders before reading the stripe-signature value; then call .get on
requestHeaders while preserving the existing request body and signature flow.


let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
Comment on lines +49 to +61

Copy link
Copy Markdown
Contributor

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

Make the webhook example compile as a standalone route.

The route uses Stripe.Event and stripe without importing either symbol. handleCheckoutComplete and handleSubscriptionCancelled are also undefined in the provided example; import them or mark them explicitly as placeholders with signatures.

🤖 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 `@rules/stripe-integration-nextjs-typescript.mdc` around lines 49 - 61, Update
the standalone POST webhook example to import the Stripe namespace/type and the
configured stripe client used by constructEvent. Also resolve
handleCheckoutComplete and handleSubscriptionCancelled by importing them from
their actual module or declaring clearly typed placeholder handlers, so the
complete route compiles without undefined symbols.

)
} catch {
return new Response("Webhook signature verification failed", { status: 400 })
}

switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session
await handleCheckoutComplete(session)
break
}
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription
await handleSubscriptionCancelled(sub)
break
Comment on lines +67 to +76

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)rules/stripe-integration-nextjs-typescript\.mdc$|stripe|webhook' || true

echo "== target file size and relevant sections =="
wc -l rules/stripe-integration-nextjs-typescript.mdc 2>/dev/null || true
sed -n '1,130p' rules/stripe-integration-nextjs-typescript.mdc 2>/dev/null | cat -n

echo "== related references =="
rg -n "webhook|idempotent|idempotency|Stripe\\.|handles?|checkout.session.completed|signature|retry|200|database|async_mutex|prisma|supabase|firebase" rules/stripe-integration-nextjs-typescript.mdc . --glob '!node_modules' --glob '!dist' --glob '!build' 2>/dev/null | head -200

Repository: PatrickJS/awesome-cursorrules

Length of output: 31364


🌐 Web query:

Stripe webhook duplicate events retry deduplication idempotency node documentation

💡 Result:

To handle duplicate Stripe webhook events in a Node.js integration, you must implement idempotency logic to ensure your application handles the same event only once, even if Stripe delivers it multiple times [1][2][3]. Key Concepts and Strategy: 1. Use the Event ID as the Deduplication Key: Every Stripe webhook event includes a unique ID (e.g., evt_123) in the payload [1][3]. This ID remains consistent across all retry attempts, making it the correct key for deduplication [3]. Do not confuse this with the Idempotency-Key header, which is used for API requests you send to Stripe to prevent duplicate actions (e.g., double-charging) [3][4][5]. 2. Atomic Processing: To prevent race conditions, use an atomic operation to record the event ID in your database (e.g., PostgreSQL or Redis) [6][3]. A common pattern is to use an INSERT statement with a unique constraint on the event ID (e.g., ON CONFLICT DO NOTHING) [6][3][5]. This ensures that if two identical requests arrive simultaneously, only one will succeed in marking the event as processed [3][5]. 3. Immediate 2xx Response: Your webhook endpoint should return a 2xx HTTP status code as quickly as possible, ideally before performing complex business logic [1][7]. This signals to Stripe that the event was received, preventing unnecessary retries [3]. 4. Implementation Pattern: - Receive and verify the event signature using Stripe's official library (stripe.webhooks.constructEvent) [1][8]. - Attempt to atomically insert the event.id into your "processed_events" database table [6][9]. - If the insert fails because the ID already exists, the event has already been processed; return a 200 OK immediately and exit [6][3]. - If the insert succeeds, proceed with your business logic (e.g., updating database records) [6]. 5. TTL and Storage: You do not need to store event IDs indefinitely. A duration covering Stripe's retry window (typically up to 3 days) plus a safety margin is generally sufficient [3]. Important Distinctions: - Webhook vs. API Idempotency: Incoming Stripe events require you to perform deduplication [3][5]. Outgoing API requests to Stripe should use the Idempotency-Key header to ensure your actions are not duplicated if your request is retried [4][5]. - Thin vs. Snapshot Events: During migration periods, you may receive both thin and snapshot versions of an event [9]. Use the provided snapshot_event ID field in thin events as your idempotency key to link them and prevent redundant processing [9].

Citations:


Implement durable webhook deduplication before dispatch.

This route dispatches after signature verification but does not atomically track event.id, so Stripe retries can invoke handlers again. Persist/claim event.id atomically, return 200 for already-processed events, and preserve the claim for the business work path so failures are retriable.

🤖 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 `@rules/stripe-integration-nextjs-typescript.mdc` around lines 67 - 76, Update
the webhook flow before the event.type switch to atomically claim and persist
event.id after signature verification. Return HTTP 200 immediately when the
event was already claimed or processed, and retain the claim through dispatch so
handler failures leave the event eligible for retry rather than marking it
successfully completed.

}
}

return new Response("ok")
}
```

## Customer Portal
```ts
export async function createPortalSession(customerId: string, returnUrl: string) {
return stripe.billingPortal.sessions.create({
customer: customerId,
return_url: returnUrl,
})
}
```

## Environment Variables
```env
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PRICE_ID=price_...
```

## Rules
- Webhook body: always read as raw text (req.text()), never parse as JSON before constructEvent
- Idempotent handlers: webhooks can fire multiple times — check if already processed before writing to DB
- Fast response: return 200 immediately, offload heavy work to a queue if needed
- Metadata: always attach userId to both metadata and subscription_data.metadata
- Never trust client-submitted amounts — verify server-side via webhook
- Never store raw card data — delegate PCI scope to Stripe
- Never skip signature verification — call constructEvent on every webhook
Loading