Customer Paid on Stripe but Has No Access: How to Diagnose and Fix It

SendPromptly
6 min read

The customer emails you. They paid ten minutes ago. Their card was charged. They have a Stripe receipt in their inbox. But when they log in, the app still shows them the free tier — no features, no plan, nothing.

This is a post-payment incident. Stripe fulfilled its half of the transaction. Your app did not fulfill its half. Here is how to work through it quickly and safely.

What You Are Actually Diagnosing

There are only a few places where the chain can break:

  1. The webhook was not delivered — Stripe attempted delivery and failed (or your endpoint was misconfigured)
  2. The webhook was delivered but not processed — your endpoint received it, but the handler did not run
  3. The handler ran but the business effect was not applied — a background job failed, a database transaction was rolled back, or the logic path was wrong
  4. The business effect was applied but to the wrong account — a lookup failure associated the payment with a different user

The goal of diagnosis is to identify which step broke. Each one has a different fix.

Step 1: Check the Stripe Event

Open the Stripe dashboard. Go to Developers → Events and find the relevant event — most likely checkout.session.completed for a one-time payment or invoice.paid for a subscription charge.

Look at:

  • Delivery status: Did Stripe deliver it? If the status is failed or pending, the event never reached your app.
  • Endpoint: Which endpoint received it? Confirm this is your production endpoint, not a staging or test environment.
  • Response code: What did your app return? A 4xx indicates your handler rejected it. A 5xx indicates an error. A 200 means receipt — but not fulfillment.
  • Retry history: Has Stripe tried to deliver this event multiple times? Multiple failed retries indicate a persistent handler failure.

If Stripe shows a failed delivery with no successful attempt, your app never received the event. The fix is to repair the endpoint configuration and then replay the event.

If Stripe shows a successful delivery but your app has no record, switch to the webhook delivered but nothing happened checklist.

Step 2: Check Your Application Logs

If Stripe shows a successful delivery (200 response), move to your application logs.

Find your logs from the minute the webhook was received. Look for:

  • The arrival of the webhook (your handler should log this)
  • Job enqueue confirmation (if you process asynchronously)
  • Job execution logs
  • Any exceptions or error messages

If the webhook arrived but no job was enqueued, your handler received the event and dropped it. If the job was enqueued but never executed, your queue workers were down or the job failed. If the job executed but the business effect is missing, the logic itself failed.

Each of these has a different cause and a different fix.

Step 3: Find the Customer’s Account

Regardless of where the failure happened, you now need to identify the customer’s account in your system and verify what state it is in.

In the Stripe event payload, find:

  • customer — the Stripe Customer ID
  • customer_email — the email used during checkout
  • metadata — any user_id or internal reference you set when creating the checkout session

Use this to look up the customer in your database. Confirm:

  • Does a user account exist for this email?
  • What plan or access level does that account show?
  • Is there any record of this payment in your system (charge ID, invoice ID, etc.)?

If no user account exists, the customer may have paid without having registered first — a flow problem that requires a different resolution. If the account exists but shows the wrong plan, the provisioning step failed.

Step 4: Choose a Repair Method

Once you have confirmed what happened, you have a few options:

Option A: Replay the Stripe webhook

If the event was not delivered or the handler failed, and you have fixed the underlying issue, replay the event from the Stripe dashboard (Developers → Events → Resend). Your handler will run again with the original payload.

This is the preferred approach because it uses the same code path that was intended to handle the event. It produces a verifiable, logged outcome.

Requirements: Your handler must be idempotent (safe to run twice). If the first attempt partially completed and wrote any state, verify that replaying will not double-apply effects.

For the replay workflow, follow the safe Stripe webhook replay guide. For handler design, use the Stripe webhook idempotency guide.

Option B: Trigger a manual repair through your app

If you have an admin interface or internal tool that can grant access or update plan state, use it. Document what you did and when. Prefer this over a direct database edit because it goes through your application’s validation and state management layers.

Option C: Direct database edit

This is a last resort. Direct database edits bypass your application logic, can leave related state inconsistent (e.g., updating a subscription but not the associated features table), and leave no audit trail unless you add one manually.

If you must edit the database directly:

  1. Take a snapshot of the current state before editing
  2. Make the minimum change required (do not fix other things while you are in there)
  3. Record what you changed and why in your incident notes
  4. Verify the application state after the edit reflects what you expect

Step 5: Verify the Customer Now Has Access

After applying the fix, verify the outcome before closing the incident:

  • Log in as the customer (or check their account from the admin view) to confirm they have the correct plan
  • Check that all gated features are now available
  • Look for any downstream effects that may still be wrong (credits balance, seat count, trial expiry, etc.)

Communicating with the Customer

While you are diagnosing, respond to the customer quickly even if you do not yet have a resolution:

“We can see your payment was successful — there is a processing issue on our end and I am investigating now. I will update you as soon as it is resolved. You will not be charged again and your access will be corrected.”

Once you have applied the fix:

“Your access has been restored. You can log in now and should see the correct plan. If anything looks wrong, reply here and I will fix it immediately.”

Customers who are handled quickly and honestly are far less likely to dispute the charge or cancel. The actual payment failure is usually less damaging than the support experience around it.


The gap between a confirmed Stripe payment and a customer actually having access is where most SaaS support fires start. SendPromptly instruments that gap — it detects when a checkout.session.completed event does not result in a provisioned customer within your expected window, and opens an incident before the customer has to email you. See how it works →