Log the failure shape, not the request payload. A SaaS error record should tell you which operation failed, where it failed, whether it can be retried, and which safe correlation ID connects related events. It should not become a second database of access tokens, webhook signatures, card data, email addresses, or free-form exception payloads.
This matters before launch because the fastest way to diagnose a login, checkout, or webhook problem is often to add a broad console.error(error). That can also copy provider response bodies, request headers, customer fields, or secrets into a system with different access and retention rules. The useful alternative is a small, deliberate event contract plus redaction at the boundary where untrusted or provider-owned data first enters your logger.
Start with a signal-first event contract
A log line should answer a narrow operational question: what happened, to which non-sensitive component, with what outcome, and how can an operator correlate it with related work? Start with fields you can explain to a future teammate.
- Event name: a stable action such as
payment.webhook.signature_invalidorauth.email_send_failed. - Severity: an intentional level such as error, warn, or info.
- Timestamp and deployment context: normally supplied by the runtime, plus a release or environment label when it is safe and useful.
- Route or operation: a normalized route name or internal operation name, never a complete URL with user-controlled query values.
- Outcome: a safe error category, HTTP status, retryability, and elapsed time where available.
- Correlation ID: an opaque request, job, or event identifier created for troubleshooting—not an access token, session value, or customer email.
OWASP’s logging guidance supports recording event type, severity, outcome, reason, and status while treating access tokens, session identifiers, passwords, connection strings, payment-card data, and sensitive personal data as values that should normally be removed, masked, sanitized, hashed, or encrypted before logging. That is a useful default even when a small app has no formal compliance program.
Make the exclusion list explicit
“Do not log secrets” is too vague for a code review. Write a denylist that a developer can apply when adding a route, provider call, or background task.
Never record the raw value
- Credentials and secrets: API keys, bearer tokens, cookies, password-reset and magic-link URLs, webhook signing secrets, database URLs, and environment-variable values.
- Authentication material: authorization headers, session IDs, CSRF values, OAuth authorization codes, refresh tokens, and complete callback query strings.
- Payment material: cardholder data, payment method details, full provider payloads, receipts that contain customer details, and Stripe signature headers.
- Direct personal data: names, email addresses, phone numbers, postal addresses, uploaded documents, and free-form support text unless there is a documented, necessary, access-controlled reason.
An identifier is not automatically safe. A provider event ID can be useful for support and replay, but an email address embedded in a route parameter is still personal data. Where correlation is needed, prefer an internal opaque ID. If you need to group activity by a session or user, use a dedicated stable internal reference or a deliberately designed keyed hash; do not improvise by printing the raw value.
Redact before the logger, not after the incident
The most dependable place to apply this rule is a server-side logging wrapper. Routes and adapters give it a small, structured object; the wrapper removes known sensitive keys, truncates unbounded strings, and rejects unexpected nested objects. That keeps the review question simple: does this event contain only the approved diagnostic fields?
type LogFields = { event: string; requestId?: string; status?: number; providerEventId?: string; errorCode?: string; retryable?: boolean; }; export function logFailure(fields: LogFields): void { console.error(JSON.stringify({ level: "error", ...fields, occurredAt: new Date().toISOString() })); }This example is deliberately restrictive. It accepts no raw Error, request object, headers, body, or arbitrary metadata. A production wrapper can add a reviewed error classifier that maps known provider failures to safe codes. It should not serialize an unknown error object “just in case.” Test the classifier with representative provider errors before relying on it.
Apply the policy to payment webhooks
Payment webhooks are a common pressure point because a signature failure is urgent and the payload is tempting to print. Stripe’s webhook verification guidance requires the raw request body for signature verification. Keep that raw body inside verification; it is not diagnostic output.
The current Accelerator product checkout follows that broad shape in app/api/webhooks/payment/route.ts: it reads the raw body, asks the payment adapter to verify the signature, maps supported events, and catches failures. The route currently passes the caught error message to console.error. That is repository evidence for a review target, not proof that the message is always safe. A provider’s wording can change, and an error can include more context than its name suggests.
For a signature failure, log a fixed event name, the provider name, a safe request ID, the response status, and a coarse reason such as signature_invalid. Do not log the stripe-signature header, raw body, or parsed event object. For a mapped business failure, add the provider event ID only after confirming it is safe to retain and useful for reconciliation.
Use this secret-safe logging checklist
Use this as the original asset for every new failure path before it reaches production.
- Name the operator question. Write the exact question the event should answer, such as “Did verification fail before event mapping?”
- Choose the smallest event schema. Include event name, status, component, safe correlation ID, and retryability only when each field changes an operator decision.
- Classify every candidate field. Mark it approved, transformed, or prohibited. Treat headers, bodies, URLs with query strings, provider responses, and
Errorobjects as prohibited until reviewed. - Redact at the server boundary. Pass primitives to one wrapper; do not rely on each call site remembering to delete fields.
- Test hostile input. Feed the wrapper a fake bearer token, email address, password-reset URL, payment-like value, and nested provider response. Assert none reach the captured output.
- Set access and retention deliberately. Decide who can read runtime logs, how long they remain available, and what happens when a log is exported to another service.
- Review on change. Re-run the checklist when adding a provider, changing an error handler, enabling debug mode, or routing logs to a new destination.
Keep errors useful without making them public
Redaction is not a reason to return empty errors to users. Keep the user-facing response generic and actionable, keep the detailed safe category in internal logs, and attach an opaque support or request ID when a user may need help. That creates a path from a report to an event without exposing the internal cause, credentials, or provider payload.
The same split helps with Next.js Server Actions. The secure Server Actions guide covers the request-side controls; this logging policy covers what remains safe to observe when those controls or their dependencies fail. Pair it with the production-readiness checklist so logging, access, error handling, and incident ownership are treated as one operating decision.
What this does and does not solve
A narrow event schema reduces accidental disclosure. It does not replace authorization, transport security, secret rotation, payment-provider controls, monitoring, or an incident response process. It also cannot make a third-party logging destination appropriate by itself. Review that destination’s access model, retention, export paths, and data-handling terms before sending it production events.
Frontend Accelerator gives solo developers a structured Next.js SaaS foundation with server-side boundaries, payment adapters, and explicit project instructions. It does not remove the responsibility to decide what is safe to log. See the Frontend Accelerator features if you want the recurring SaaS structure in place while keeping operational decisions visible and reviewable.



