Audit logging of failed payment attempts for security monitoring
Why it matters
Failed payment attempts are the primary signal for detecting carding attacks, credential stuffing, and account takeover attempts on your checkout. Without server-side audit logs, you cannot identify patterns — for example, 500 failed attempts against different card numbers from the same IP over one hour, or repeated failures on the same user account from different countries. PCI-DSS 4.0 Req 10.2 mandates logging of access control failures and user authentication attempts. CWE-778 (Insufficient Logging) and OWASP A09 (Security Logging and Monitoring Failures) both target exactly this gap. Client-side console.log calls are ephemeral and invisible to your backend — they don't count as audit logging.
Severity rationale
Low because absent server-side payment failure logs eliminate the primary data source for detecting carding attacks, making fraud patterns invisible until they escalate to chargebacks.
Remediation
Add structured server-side logging in every payment error catch block. Include enough context for pattern detection without logging PII or card data.
} catch (err) {
if (err instanceof Stripe.errors.StripeCardError) {
console.error(JSON.stringify({
event: 'payment_failed',
timestamp: new Date().toISOString(),
error_code: err.code,
decline_code: err.decline_code,
payment_intent_id: paymentIntentId,
amount_cents: amount,
currency,
user_id: session?.userId ?? 'anonymous',
// Do NOT log: card number, expiry, CVC, full email, full name
}))
return NextResponse.json({ error: 'Payment declined.' }, { status: 402 })
}
}
Also handle the payment_intent.payment_failed webhook event to capture declines that occur asynchronously. Ship these structured logs to your observability platform (Datadog, Grafana, CloudWatch) and set an alert when failure rate exceeds a threshold.
Detection
-
ID:
failed-payment-audit-log -
Severity:
low -
What to look for: Count every catch block or error handler in payment API routes. For each, check whether a structured log entry is produced that includes at least 3 of: timestamp, error code, amount attempted, payment method type (not the card number), user/session identifier, IP address. Also check whether webhook events for
payment_intent.payment_failedor charge disputes are handled and logged. Report the count: "X of Y payment error handlers include server-side audit logging." -
Pass criteria: Failed payment attempts are logged server-side with sufficient context for security review: at least 3 data fields (timestamp, error code, session identifier at minimum). Report even on pass: "X payment error paths have structured logging with Y average fields per entry."
-
Fail criteria: Failed payment attempts are not logged at all, or are only logged to the browser console (which is client-side and ephemeral).
-
Skip (N/A) when: The project uses a fully hosted payment form where failed attempts are tracked entirely by the provider, and your backend receives no failed-attempt events.
-
Detail on fail: Describe what is missing. Example:
"Payment failure catch blocks in app/api/payment/route.ts contain no logging — failed attempts produce no server-side record, making fraud pattern detection impossible" -
Remediation: Add structured logging for all payment failures:
} catch (err) { if (err instanceof Stripe.errors.StripeCardError) { // Structured audit log — no PII, but enough for pattern detection console.error(JSON.stringify({ event: 'payment_failed', timestamp: new Date().toISOString(), error_code: err.code, decline_code: err.decline_code, payment_intent_id: paymentIntentId, amount_cents: amount, currency, user_id: session?.userId ?? 'anonymous', // Do NOT log: card number, expiry, CVC, email, full name })) return NextResponse.json({ error: 'Payment declined.' }, { status: 402 }) } }Consider also handling
payment_intent.payment_failedin your Stripe webhook to capture declines that happen asynchronously.
External references
- cwe · CWE-778 — Insufficient Logging
- owasp:2021 · A09
- pci-dss:4.0 · Req 10.2 — Audit logs capture all individual user access to cardholder data
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ecommerce-payment-security·automated