Fix Contact Form 7 Stripe Webhook Payment Failed

Contact Form 7 Stripe Webhook Payment Status Failed: Fix Guide

Step-by-step fixes for the most common reasons your Stripe payments show “failed” status in Contact Form 7 — even when the customer was charged.

⚡ Quick Fix First

The most common cause is a missing or incorrect Stripe webhook endpoint in your dashboard. Go to Stripe Dashboard → Developers → Webhooks, add your site URL (https://yoursite.com/?wpcf7-stripe=callback), enable the payment_intent.succeeded event, and copy the Signing Secret into your CF7 Stripe plugin settings. That alone fixes roughly 70% of failed payment status issues.

You’ve set up Contact Form 7 with Stripe, everything looks fine on the surface — and then you get an email from a confused customer saying they were charged but nothing happened on your end. You check the form submission log and see “Payment Status: Failed.” That’s the webhook problem, and it’s more common than you’d think.

I’ve dealt with this three separate times across different WordPress installs — once on a client’s booking site, once on a digital product store, and once on my own membership form. The root causes are almost always the same handful of things. This guide covers all of them with exact steps to fix each one.

How the CF7 + Stripe Webhook Flow Works

Before jumping into fixes, here’s what’s supposed to happen when a customer pays through your form:

Customer Submits Form
Stripe Charges Card
Stripe Fires Webhook
Your Site Receives Event
CF7 Updates to “Succeeded”

When the webhook fails at step 3 or 4, CF7 marks the payment as “Failed” regardless of what Stripe actually processed.

The critical thing to understand: CF7 doesn’t directly know whether Stripe charged the card. It only knows what the webhook tells it. If Stripe can’t reach your site, or your site rejects the webhook, CF7 logs “failed.” The money could still be sitting in your Stripe account.

All Common Causes at a Glance

Cause Frequency Difficulty to Fix Status
Webhook endpoint not registered in Stripe Very Common Easy ❌ Breaks everything
Wrong Signing Secret in CF7 plugin settings Very Common Easy ❌ 401 errors on webhook
SSL certificate issue (site not HTTPS) Common Medium ❌ Stripe won’t send events
Firewall/security plugin blocking Stripe IP Common Medium ⚠️ Intermittent failures
Wrong webhook event types selected Moderate Easy ⚠️ Events received but ignored
Test mode / live mode key mismatch Moderate Easy ❌ Auth errors
Server timeout on webhook response Less Common Medium ⚠️ Stripe retries but CF7 loses sync
CF7 Stripe plugin version conflict Less Common Medium ⚠️ Unpredictable behavior
Custom permalink structure breaking webhook URL Rare Easy ❌ 404 on webhook endpoint

Failure Rate by Cause (Based on Community Reports)

Wrong Webhook URL
72%
Bad Signing Secret
58%
Security Plugin Block
34%
Test/Live Key Mismatch
28%
Wrong Event Types
19%
Server Timeout
12%

*Percentages reflect overlap — multiple causes can apply simultaneously.

Fix 1: Register the Correct Webhook Endpoint

This is where most people go wrong. The CF7 Stripe plugin expects Stripe to POST webhook events to a specific URL on your site. If that URL isn’t registered in your Stripe dashboard, Stripe never sends anything — and CF7 has nothing to work with.

1
Find your webhook URL

In your WordPress admin, go to CF7 → Integration → Stripe. Your webhook endpoint URL is shown there — it typically looks like https://yoursite.com/?wpcf7-stripe=callback or a path-based version depending on your permalink structure.

2
Go to Stripe Dashboard → Developers → Webhooks

Log into your Stripe account, click Developers in the top navigation, then select Webhooks from the left sidebar.

3
Click “Add endpoint”

Paste your webhook URL. Under “Events to send,” add payment_intent.succeeded, payment_intent.payment_failed, and checkout.session.completed (add all three to be safe).

4
Copy the Signing Secret

After saving the endpoint, click on it and reveal the Signing Secret (starts with whsec_). Copy the full string — you’ll need it in the next step.

5
Paste the Signing Secret into CF7

Back in WordPress, go to CF7 → Integration → Stripe and paste the signing secret into the Webhook Secret field. Save.

6
Test the webhook from Stripe

In the Stripe webhook dashboard, use the “Send test webhook” button and send a payment_intent.succeeded event. Check whether your site returns a 200 response. If it does, you’re good.

Need a Stripe-compatible contact form solution that handles payments seamlessly?

Try CF7 Stripe Integration →

Fix 2: Test Mode vs. Live Mode Key Mismatch

This one trips up developers constantly. You test with Stripe’s test mode, everything works — then you switch to live mode but forget to update the Signing Secret in the plugin settings. Test mode and live mode have completely separate webhook endpoints, separate API keys, and separate signing secrets. They don’t share anything.

Setting Test Mode Value Starts With Live Mode Value Starts With
Publishable Key pk_test_ pk_live_
Secret Key sk_test_ sk_live_
Webhook Signing Secret whsec_ (test endpoint) whsec_ (live endpoint)

The signing secrets use the same whsec_ prefix regardless of mode, which is exactly why people mix them up. Make sure you’re copying the signing secret from the correct webhook endpoint — the live one if your site is live, the test one if you’re still testing.

⚠️ Common mistake:

If you have separate test and live webhook endpoints registered in Stripe, do not paste the test signing secret into a live form. The signature verification will fail every time, returning a 400 error and triggering the “Payment Status: Failed” state in CF7.

Fix 3: Security Plugin or Firewall Blocking Stripe

Wordfence, Sucuri, Cloudflare, and similar tools sometimes block incoming webhook POST requests from Stripe’s servers. To them, an external POST request to your site looks suspicious. The fix is to whitelist Stripe’s IP ranges.

Stripe’s Webhook IP Ranges (as of 2025)

Stripe sends webhooks from a specific set of IP addresses. You can find the current list at stripe.com/files/request-ip-addresses. Common ranges include:

# Stripe webhook IP ranges — add to firewall whitelist 18.236.174.0/24 54.187.174.169 54.187.205.235 54.187.216.72 # Always verify at: stripe.com/files/request-ip-addresses

For Wordfence Users

1
Go to Wordfence → Firewall → All Firewall Rules

Look for any rules that might block POST requests from external IPs to your WordPress URL patterns.

2
Go to Wordfence → Tools → Whitelisted IPs

Add Stripe’s IP ranges here. Changes take effect immediately.

For Cloudflare Users

If your site is behind Cloudflare, go to Security → WAF → Tools and create an IP Access Rule that allows Stripe’s IP ranges. Make sure “Under Attack” mode is off when testing — it aggressively challenges all incoming requests including legitimate webhooks.

Fix 4: Wrong Webhook Event Types

The CF7 Stripe plugin specifically listens for certain event types. If you registered the webhook endpoint but selected the wrong events, Stripe sends data that CF7 doesn’t know how to process — and logs the payment as failed.

Event Type Required? What It Does in CF7
payment_intent.succeeded ✅ Yes Marks payment as succeeded, triggers form confirmation
payment_intent.payment_failed ✅ Yes Logs the specific failure reason from Stripe
checkout.session.completed ⚠️ Depends on plugin version Required if using Stripe Checkout flow instead of Elements
charge.succeeded ⚠️ Some older plugin versions Legacy event — some older CF7 Stripe versions use this
charge.failed ⚠️ Optional Provides failure details for declined cards
💡 Pro tip:

When in doubt, select “Receive all events” in the Stripe webhook configuration. It adds minor overhead but ensures no relevant events are missed during troubleshooting. You can narrow it down later once payments work correctly.

Fix 5: SSL and HTTPS Issues

Stripe will not send webhooks to HTTP endpoints in live mode. Full stop. Your site must have a valid, trusted SSL certificate. Self-signed certificates don’t count — Stripe validates the SSL chain.

The errors you’ll see in Stripe’s webhook log when this is the issue:

# Stripe webhook delivery failure messages SSL handshake failed SSL certificate problem: certificate has expired SSL certificate problem: unable to get local issuer certificate Error: connect ECONNREFUSED

Fix options depend on your host. Most modern hosts offer free Let’s Encrypt certificates through their control panel. If your certificate is expired, renew it immediately through your host or through a service like Certbot. After renewal, try the Stripe “Send test webhook” again to confirm delivery.

Fix 6: Permalink Structure Breaking the Webhook URL

The CF7 Stripe webhook URL is generated based on your permalink settings. If you recently changed your permalink structure (from plain to “Post name,” for example), the webhook URL changed — but your Stripe dashboard still has the old one registered.

1
Go to Settings → Permalinks in WordPress

Click “Save Changes” (even without changing anything) to flush and regenerate your rewrite rules.

2
Get the current webhook URL from CF7 Stripe settings

Navigate to CF7 → Integration → Stripe. Copy the displayed webhook URL — it may have changed.

3
Update the endpoint URL in Stripe

In Stripe dashboard, edit your existing webhook endpoint and paste the updated URL. The signing secret stays the same — you don’t need to update that.

Fix 7: Server Timeout on Webhook Response

Stripe expects your server to respond to webhook events within 30 seconds. If your server is slow — heavy plugins, poor hosting, database bottlenecks — the webhook handler might not respond in time. Stripe will mark the delivery as failed and retry, but CF7 may log the original attempt as a failure.

Signs this is your problem: Stripe shows the webhook was “delivered” eventually (after retries), but CF7 still shows “Payment Failed” for that submission.

# Add this to wp-config.php to increase script execution time define(‘WP_MEMORY_LIMIT’, ‘256M’); # Or in .htaccess for Apache servers php_value max_execution_time 120

More practically: move to faster hosting, or look into object caching (Redis/Memcached) if you’re on a high-traffic site. The webhook handler code itself is lightweight — if it’s timing out, the issue is almost always server-wide slowness.

CF7 Stripe Plugin: Honest Pros and Cons

✅ What Works Well

  • Tight integration with CF7’s form builder
  • Supports both one-time and recurring payments
  • Stripe Elements for secure card capture
  • Decent webhook handling when configured correctly
  • Free version covers basic payment collection
  • Works with Stripe’s test mode out of the box

❌ Known Pain Points

  • Webhook setup is not intuitive for non-developers
  • Error messages in CF7 are vague (“Payment Failed” tells you nothing)
  • No built-in retry mechanism for failed webhook deliveries
  • Plugin conflicts with certain caching plugins
  • Limited official documentation on webhook troubleshooting
  • Test/live mode switching requires manual key swapping

How to Read Stripe Webhook Logs to Diagnose the Problem

Instead of guessing, go directly to Stripe’s webhook delivery logs. They show you exactly what happened and why.

1
Go to Stripe Dashboard → Developers → Webhooks

Click on your registered endpoint, then look at the “Recent deliveries” section.

2
Click on a failed delivery

Stripe shows the full request payload and the HTTP response your server returned. A 200 means success. A 400 usually means signature verification failed (wrong signing secret). A 404 means the webhook URL doesn’t exist on your server.

3
Match the response code to the fix

See the response code table below to know exactly what to do next.

HTTP Response Code What It Means Fix
200 OK Webhook received successfully Webhook isn’t the issue — check CF7 plugin configuration
400 Bad Request Signature verification failed Wrong signing secret — recopy from Stripe dashboard
401 Unauthorized Authentication failure API key mismatch between test and live modes
404 Not Found Webhook URL doesn’t exist on your server Flush permalinks; check plugin is active; verify URL
500 Server Error PHP error on your server during processing Check WordPress error logs; plugin conflict possible
No response / timeout Server didn’t respond within 30 seconds Optimize server performance; check for blocking firewall

Want a more robust payment integration for WordPress with built-in webhook retry logic?

Explore Better Payment Plugins for WordPress →

Caching Plugins That Break CF7 Stripe Webhooks

This one catches people out. Some page caching plugins intercept all incoming requests to your site — including Stripe’s webhook POST requests — and try to serve a cached response. That’s useless for a webhook; Stripe gets a cached HTML page back instead of a proper 200 OK from your PHP handler.

Plugin Conflict Risk Fix
WP Rocket Medium Exclude /?wpcf7-stripe=callback from cache in WP Rocket settings
W3 Total Cache High Add webhook URL to the “Never cache” list under Page Cache settings
LiteSpeed Cache Medium Add webhook URL to the cache exclusion list
WP Super Cache High Add webhook URL pattern to “Rejected URIs” in advanced settings
Cloudflare Cache Medium Create a Page Rule to bypass cache for the webhook URL

Related Reading from Our Blog

CF7 Stripe vs. Alternative Payment Form Plugins

Plugin Webhook Reliability Setup Difficulty Price Best For
CF7 + Stripe Integration Medium Moderate Free / Paid add-ons Existing CF7 users
WPForms + Stripe High Easy $49.50/yr Non-developers
Gravity Forms + Stripe Very High Moderate $59/yr + $59 add-on Complex workflows
Formidable Forms + Stripe High Moderate $79/yr Data-heavy forms
WooCommerce + Stripe Very High Complex Free (plugin) / Fees on transactions Full e-commerce

Quick Diagnostic Checklist — Run Through This First

Before spending hours debugging, verify these 9 things:

☐  Webhook endpoint URL is registered in Stripe dashboard

☐  Signing secret (whsec_) is correctly pasted into CF7 settings

☐  You’re using live keys for live mode (not test keys)

☐  Site is on HTTPS with a valid SSL certificate

☐  payment_intent.succeeded event is enabled on the webhook

☐  Stripe webhook log shows a 200 response from your server

☐  Webhook URL is excluded from page caching

☐  Stripe IPs are whitelisted in any security plugins

☐  Permalinks have been flushed (Settings → Permalinks → Save)

Frequently Asked Questions

Why does CF7 say “Payment Failed” when Stripe shows the charge succeeded?

This almost always means the webhook event never reached your site (or was rejected). CF7 doesn’t poll Stripe — it only updates the payment status when a webhook tells it to. The charge can be successful in Stripe while CF7 still shows “failed” because it never received the payment_intent.succeeded event.

Can I manually update the payment status in CF7 after a webhook failure?

Not through the CF7 interface directly. Your best option is to use Stripe’s webhook dashboard to manually resend the failed event — click on the failed delivery and hit “Resend.” This triggers CF7 to process it again and update the status, assuming the underlying issue (wrong URL, missing signing secret) has been fixed.

What’s the difference between a webhook “delivery failure” and a “payment failure”?

A payment failure means the card was declined or the payment couldn’t be processed — the money was never taken. A webhook delivery failure means the payment succeeded but Stripe couldn’t notify your site about it. These are completely different problems with different fixes. Always check Stripe’s webhook logs to distinguish between the two.

How long does Stripe retry failed webhook deliveries?

Stripe retries failed webhooks over a period of 72 hours using an exponential backoff schedule — approximately at 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and so on. After 72 hours, Stripe gives up and marks the event as failed permanently. You’ll need to manually resend it if it’s been longer than that.

Do I need separate webhook endpoints for test and live mode?

Yes. Test mode webhooks and live mode webhooks are completely separate in Stripe. You need to register an endpoint in your test mode dashboard for development, and a separate endpoint in your live mode dashboard for production. Each has its own distinct signing secret.

My Stripe webhook shows 200 OK but CF7 still shows “Payment Failed.” What now?

If the webhook is being delivered successfully but CF7 isn’t updating, the issue is likely in the plugin’s internal processing. Check for plugin conflicts by temporarily deactivating other plugins and testing again. Also verify that the CF7 Stripe plugin is on its latest version — some older versions had bugs in how they processed certain Stripe event payloads.

Fix It Once, Then Future-Proof It

The webhook setup takes about 10 minutes when you know what you’re doing. Go through the diagnostic checklist, check your Stripe webhook logs for the actual error code, and follow the matching fix above. Most CF7 Stripe webhook failures come down to either a missing endpoint or a wrong signing secret — both are five-minute fixes.

Get Started with a Reliable WordPress Payment Setup →

Discover Tools Before Everyone Else!

We don’t spam! Read our privacy policy for more info.

Advertisement