All 22 checks with why-it-matters prose, severity, and cross-references to related audits.
Skipping an explicit initial booking status causes payment-confirmation race conditions: if a booking defaults to `'confirmed'` at insert time, a failed or pending payment leaves a confirmed reservation with no corresponding charge. Under CWE-841 (improper enforcement of behavioral workflow), the system's state machine collapses at the first edge case. Any downstream job that queries `status = 'CONFIRMED'` to send reminder emails or mark slots occupied will fire on unconfirmed bookings, producing phantom reservations, double-charges, or capacity miscounts that are extremely difficult to detect after the fact.
Why this severity: Medium because the defect corrupts booking state and triggers downstream side-effects, but does not directly expose customer data or enable unauthorized access.
booking-flow-lifecycle.booking-creation.initial-booking-stateSee full patternWithout a server-side availability check before insertion, two users submitting the booking form within milliseconds of each other will both receive success responses for the same slot. Frontend availability guards are client-controlled and can be bypassed entirely with a direct API call. OWASP A04 (Insecure Design) classifies trusting client-side state for server decisions as a structural flaw. A double-booked appointment slot means one customer shows up to find their slot occupied — a support and refund incident that a single server-side query would have prevented entirely.
Why this severity: Critical because the absence of server-side availability enforcement directly produces confirmed double-bookings, a business-critical data integrity failure that requires manual remediation for every occurrence.
booking-flow-lifecycle.booking-creation.conflict-checkSee full patternCreating the booking record inside the payment success webhook — rather than before initiating payment — creates a window where the customer is charged but receives no booking. Stripe webhook delivery can fail, retry with a delay, or be silently dropped by network issues. CWE-362 (race condition) and CWE-841 (behavioral workflow violation) both apply: the system has implicitly split an atomic operation into two steps with no rollback path. The result is a customer whose card was debited, a slot that may or may not be blocked, and a support queue incident that requires manual reconciliation.
Why this severity: High because webhook delivery failure after a successful charge leaves customers in a provably charged-but-unbookied state, requiring manual intervention for every incident.
booking-flow-lifecycle.booking-creation.confirmation-recordSee full patternIn multi-step booking flows, the gap between slot selection and payment completion can span several minutes of form-filling. Without a temporary hold during that window, a second user can select the same slot, complete the flow first, and the original user hits a conflict error at the payment step — after they've already entered card details. CWE-362 describes this as a classic TOCTOU (time-of-check to time-of-use) window. The user experience failure is severe: you've collected full intent and payment details from a customer only to tell them at the last step that the slot is gone.
Why this severity: Low because the primary impact is user experience degradation (failed checkout at the final step) rather than data corruption or security exposure.
booking-flow-lifecycle.booking-creation.time-slot-lockSee full patternGroup-capacity slots — classes, workshops, shared resources — require a count-against-capacity check, not just a slot-existence check. Without it, 30 attendees can book a room with a capacity of 10, because the system only asked "is the slot taken?" rather than "is the slot full?". OWASP A04 (Insecure Design) applies: the domain model includes a capacity constraint that the server never enforces. Overbooking forces refunds, damages customer trust, and requires manual communication with every affected attendee when the overbooking is discovered.
Why this severity: Medium because the defect allows overbooking of capacity-constrained resources, causing real-world operational failures and refunds, but does not expose sensitive data.
booking-flow-lifecycle.booking-creation.overbooking-preventionSee full patternWithout server-side contact field validation, malformed data reaches the database: null bytes in email fields break transactional email delivery, invalid phone numbers fail SMS delivery, and a booking with a corrupt contact record becomes impossible to reach for reminders or cancellations. OWASP A03 (Injection) covers malicious input reaching persistence; CWE-20 covers missing input validation generally. A booking where the customer cannot be contacted is, from an operational standpoint, as bad as a booking that was never created — except the slot is now blocked.
Why this severity: Medium because storing invalid contact data silently breaks the notification pipeline for every affected booking, causing operational failures without surfacing an error to the developer.
booking-flow-lifecycle.booking-creation.customer-contactSee full patternApplication-level availability checks followed by unguarded inserts are the leading cause of double bookings in production systems. Under concurrent load, two requests can both read "slot available," both pass the availability check, and both insert a booking record — all before either transaction commits. CWE-362 (race condition) and OWASP A04 (Insecure Design) both apply. A database-level UNIQUE constraint is the only mechanism that enforces the invariant atomically: the database serializes concurrent writes and rejects the duplicate at the storage layer, regardless of application concurrency.
Why this severity: Critical because application-level checks without database constraints provably fail under concurrent load, producing confirmed double-bookings that require manual remediation and refunds.
booking-flow-lifecycle.double-booking.concurrent-request-handlingSee full patternIn flows where availability is checked when the user loads the booking form and the final insert happens minutes later at payment time, the check and the commit are separated by an unbounded time window. CWE-367 (TOCTOU race condition) names this exact pattern: the condition checked and the action taken are not atomic. A second user who books the same slot during the payment flow will not be detected until the UNIQUE constraint fires — and by then the first user has already been charged or committed their card details. Re-checking inside the transaction collapses the window to zero.
Why this severity: High because the TOCTOU window between initial check and final insert is exploitable under normal concurrent usage, not just adversarial load.
booking-flow-lifecycle.double-booking.slot-availability-recheckSee full patternFor multi-step flows with meaningful capacity constraints, optimistic locking (version fields) surfaces conflicts only at the final commit — after the user has filled the booking form and possibly entered payment details. CWE-362 (race condition) describes the hazard: concurrent requests succeed their availability checks, race to commit, and one fails at the very last step. Pessimistic locking with `FOR UPDATE` queues concurrent requests sequentially, so each one sees the committed state of the previous one and the user receives immediate feedback before investing time in the flow.
Why this severity: Low because UNIQUE constraints provide the primary double-booking defense; pessimistic locking reduces late-stage UX failures but is not the sole concurrency control.
booking-flow-lifecycle.double-booking.pessimistic-lockingSee full patternBooking creation typically writes to multiple tables: the booking record, the slot's capacity counter, a payment reference, and possibly an audit log. Sequential `await db.*.create/update` calls outside a transaction are not atomic — if any step fails, all previous writes remain committed. CWE-362 and CWE-841 both apply: partial writes leave orphaned booking records with no slot decrement, or a slot marked unavailable with no corresponding booking. These orphaned records accumulate silently and are only discovered when a customer reports a missing booking or a slot appears permanently blocked.
Why this severity: Critical because non-atomic multi-table writes produce orphaned records on partial failure, causing silent data corruption that is difficult to detect and expensive to remediate.
booking-flow-lifecycle.double-booking.transaction-atomicitySee full patternWhen a slot reaches capacity and the system throws an unhandled database constraint violation or returns an HTTP 500, the customer receives no actionable information: they cannot retry a different slot, join a waitlist, or understand what happened. An unhandled capacity error produces the same symptom as a server crash from the user's perspective. Even without a formal waitlist feature, returning a structured 409 with a descriptive message gives the frontend enough information to guide the user — a minimum viable response to a predictable state the system already knows about.
Why this severity: Low because the primary impact is poor user experience and support volume, not data corruption or security exposure, when capacity limits are reached.
booking-flow-lifecycle.double-booking.waitlist-handlingSee full patternRescheduling endpoints often receive less scrutiny than creation endpoints, creating an asymmetry where PATCH handlers skip the availability and capacity checks that the POST handler enforces. OWASP A03 (Injection) and CWE-20 both apply when untrusted input reaches a database write without validation. A user who reschedules to a fully-booked or already-taken slot bypasses every guard that protected the original booking — producing a double-booking through the update path instead of the creation path. The fix is a shared validation function, not duplicated logic, so both paths stay in sync as the system evolves.
Why this severity: High because unvalidated reschedule updates produce the same double-booking outcome as unvalidated creation, with no difference in business impact.
booking-flow-lifecycle.lifecycle.modification-validationSee full patternCancellation that only updates a `status` string without releasing slot capacity leaves the system in a permanently inconsistent state: the booking is logically gone, but the slot's capacity counter still reflects it as active. Every subsequent availability query will report the slot as booked when it is not, blocking new reservations for capacity that has been freed. CWE-841 (improper enforcement of behavioral workflow) applies — the cancellation workflow must atomically reverse all side-effects of booking creation: status change, capacity release, and refund trigger if paid. A double-cancellation guard is equally necessary to prevent a second cancel from decrementing capacity below zero.
Why this severity: High because missing capacity release on cancellation permanently blocks available slots, causing revenue loss and incorrect availability displays for every subsequent query.
booking-flow-lifecycle.lifecycle.cancellation-state-machineSee full patternNon-atomic reschedules split a logically single operation into two sequential database calls: first release the old slot, then reserve the new one. If the new slot reservation fails (conflict, timeout, or error), the old slot has already been released — and the booking is now in an invalid state with no slot attached. CWE-362 (race condition) and CWE-841 both apply. The customer ends up with a booking record that points to no slot, or a booking that was moved to a slot that was simultaneously taken by another user. A transaction wrapping both mutations prevents this by rolling back the release if the reservation fails.
Why this severity: Medium because non-atomic reschedules produce booking records in an invalid intermediate state on any failure during the two-step operation.
booking-flow-lifecycle.lifecycle.rescheduling-logicSee full patternAllowing cancellation or rescheduling up to the moment an event starts undermines the service provider's ability to prepare: a massage therapist who learns of a cancellation 5 minutes before the appointment has no time to fill the slot, collects no revenue, and bears the full cost of the blocked time. CWE-20 applies because the handler accepts a time-based parameter (booking start time) without validating it against a business constraint. CWE-841 applies because the behavioral workflow — "changes require N hours notice" — is defined in business policy but absent from the code. Client-provided timestamps must never be used for the deadline calculation; only server-side `Date.now()` is trustworthy.
Why this severity: Medium because missing deadline enforcement causes predictable revenue loss for every last-minute cancellation, and the client cannot be trusted to enforce the policy on their own.
booking-flow-lifecycle.lifecycle.deadline-enforcementSee full patternWithout a history table or audit log, a booking system cannot answer the most basic support questions: "who cancelled my appointment?", "when was this booking confirmed?", or "was the reschedule requested by the customer or the provider?". ISO 25010:2011 maintainability requires that state changes are traceable. In regulated contexts (medical scheduling, legal appointments, financial services), audit trails are legally required. Even in unregulated systems, the inability to reconstruct booking history turns routine customer disputes into unresolvable conflicts and forces manual database forensics for every support ticket.
Why this severity: Info because audit logging improves operational visibility and dispute resolution but does not directly cause data corruption or security exposure when absent.
booking-flow-lifecycle.lifecycle.history-trackingSee full patternSilent state changes erode user trust and generate support load: customers who cancel, reschedule, or get confirmed without receiving a notification assume the transaction failed and either double-book, contact support, or show up to appointments that no longer exist. Missed notifications on cancellation and rescheduling are a direct user-experience failure that drives no-shows, duplicate bookings, and chargebacks. Out-of-band confirmation is also a baseline expectation under consumer protection guidance for bookings involving payment.
Why this severity: Medium because missing notifications cause measurable support and revenue loss but do not breach security, data, or regulated compliance boundaries.
booking-flow-lifecycle.lifecycle.status-updatesSee full patternCharging a card before the booking record is safely persisted and confirmed creates a class of support incidents that requires manual refunds: "I was charged but have no booking." CWE-841 (behavioral workflow violation) and CWE-362 both apply — the payment and booking writes are implicitly ordered, and that ordering is not enforced by the code. Stripe's authorize-then-capture flow (`capture_method: 'manual'`) exists precisely for this scenario: authorize the payment (reserve funds) while processing the booking, then capture (collect funds) only after the booking is guaranteed. Any other ordering introduces a window where money moves without a corresponding booking.
Why this severity: High because premature capture means every downstream booking failure — DB error, webhook delay, constraint violation — produces a charged customer with no reservation, requiring manual refund per incident.
booking-flow-lifecycle.payment.payment-capture-timingSee full patternA failed payment that leaves a `PENDING_PAYMENT` or `HELD` booking blocks the slot indefinitely: the booking appears in capacity counts, the slot shows as unavailable to other users, and the original customer cannot retry because the slot appears taken. CWE-459 (incomplete cleanup on error) names this pattern precisely. Every payment failure path — declined card, network timeout, webhook failure — must release both the booking status and the slot hold atomically. A slot left blocked by a failed payment is functionally equivalent to a permanently deleted slot from the user's perspective.
Why this severity: High because unrecovered failed-payment state permanently blocks slots for all future customers, directly reducing available inventory without any corresponding revenue.
booking-flow-lifecycle.payment.failure-handlingSee full patternA cancellation handler that updates the booking status in the database but never calls the payment provider's refund API silently keeps customer money. Customers who cancel within the refund window see a "Cancelled" status but receive no refund — the money transfer is one-way. CWE-841 (behavioral workflow violation) applies: the business workflow specifies refund-on-cancel, but the code path that executes cancellation does not include the corresponding refund step. Customers discover the missing refund days later via their bank statement, producing chargebacks that are more expensive than the original refund would have been.
Why this severity: Medium because the defect silently retains customer funds on cancellation, which is both a trust violation and a chargeback risk, though it requires cancellation of a paid booking to manifest.
booking-flow-lifecycle.payment.refund-logicSee full patternWithout bidirectional references between the booking record and the payment provider transaction, any webhook processing failure becomes an irrecoverable data loss: you cannot look up the booking from the payment event, and you cannot look up the payment from the booking record. CWE-706 (use of incorrectly-resolved name or reference) and CWE-841 both apply. This is the root cause of "customer was charged but has no booking" incidents that require manual database forensics: the payment provider has the charge, the application has the booking, but there is no programmatic link between them. Every refund, rebooking, and dispute resolution becomes a manual operation.
Why this severity: Critical because without bidirectional coupling, any webhook failure produces orphaned charges that cannot be programmatically reconciled, requiring manual intervention per incident.
booking-flow-lifecycle.payment.payment-booking-couplingSee full patternNetwork timeouts during payment API calls are a normal operational event, and every retry without an idempotency key is a potential double charge. CWE-605 (multiple binds to the same port) and CWE-841 both describe re-entrancy hazards; in payment systems the re-entrancy produces a duplicate charge that Stripe will honor because each request without an idempotency key is treated as a new, independent charge. A customer who retries a failed payment page reload can be charged multiple times. Idempotency keys that are random UUIDs regenerated on each retry defeat the purpose — the key must be deterministically derived from the booking ID so retries produce the same result.
Why this severity: Low because double-charge risk requires a specific failure mode (network timeout + retry) but the consequence — a customer charged twice — demands immediate manual remediation.
booking-flow-lifecycle.payment.retry-mechanismSee full patternRun this audit in your AI coding tool (Claude Code, Cursor, Bolt, etc.) and submit results here for scoring and benchmarks.
Open Booking Flow & Lifecycle Audit