Contact Form 7 has no built-in webhook sender. This article explains how CF7 form submissions reach a webhook URL — from the wpcf7_before_send_mail hook and raw PHP approach, through the production failures that pattern creates, to reliable no-code delivery via Webhook Actions with field mapping, async dispatch, and automatic retry.
TL;DR
wpcf7_before_send_mail manually or use a pluginContact Form 7 handles form submission natively in three ways: it sends email notifications, stores submissions optionally in the database (via Flamingo), and displays a confirmation message to the user. That is the extent of its built-in integration capabilities. There is no webhook sender, no HTTP POST action, and no API integration layer in CF7 core.
This is a deliberate design choice. CF7 positions itself as a lightweight form plugin. External integrations are explicitly delegated to third-party plugins and custom code. The plugin exposes action hooks — wpcf7_before_send_mail, wpcf7_mail_sent, wpcf7_submit — that developers use to attach custom processing.
CF7 is installed on more than 5 million active WordPress sites as of 2025. Its adoption means virtually every automation platform that targets WordPress has CF7 coverage — but that coverage is always provided by a plugin or custom code, not by CF7 itself.
For a CF7-to-webhook integration without any code, the Webhook Actions CF7 example walks through the complete plugin setup with field mapping and n8n integration.
wpcf7_before_send_mail is the primary action hook developers use to intercept CF7 submissions. It fires after the form passes validation and CF7 prepares the notification email — but before the email is sent. At this point, the full submission data is available through the WPCF7_Submission singleton.
The hook passes a single argument: the WPCF7_ContactForm object representing the form that was submitted. To access the submitted field values, you call WPCF7_Submission::get_instance() inside the callback.
Field names in get_posted_data() correspond to the name attribute you set in the CF7 form editor. A field written as [text* your-name] in the CF7 template produces a key 'your-name' in the data array. This is stable as long as you do not rename the field in the form editor — it does not change if you rearrange fields or update the label.
wpcf7_before_send_mail also passes three additional arguments if you accept them: the current mail instance, the mail 2 (reply-to) instance, and the additional mail (CC) instance. These are rarely needed for webhook dispatch but useful if you want to modify email content alongside triggering an integration.
The raw PHP implementation hooks wpcf7_before_send_mail, extracts the field values from the submission object, builds a payload array, and calls wp_remote_post(). This is the pattern you find in most CF7 webhook tutorials.
This pattern produces a working webhook for simple cases and controlled environments. The problems only emerge under production load — which we cover in the next section.
The raw PHP implementation has three structural production problems. None surface during development.
Inline execution slows the form response. wpcf7_before_send_mail runs synchronously inside the PHP request that handled the form POST. The wp_remote_post() call blocks that request until the webhook endpoint responds. On a slow n8n instance, Zapier with cold-start latency, or any endpoint under momentary load, every CF7 submission waits for that HTTP roundtrip before showing the confirmation message. This is directly visible to the person filling in the form.
Timeout failures silently drop submissions. wp_remote_post() defaults to a 5-second timeout. Endpoints that take longer — including those serving legitimate requests — produce a WP_Error that goes unlogged and unretried. The contact form entry is not stored anywhere (CF7 core does not save to a database by default), so a timed-out webhook delivery means the submission is permanently gone.
No visibility into what happened. WordPress does not log outbound HTTP calls. A failed wp_remote_post() inside a hook produces no admin notice, no PHP error log entry (unless you explicitly check the return value and log it), and no indication to the site owner. The form submitter sees the confirmation message. The webhook never arrived. No one knows.
| Aspect | Raw PHP (wpcf7 hook) | Webhook Actions (queued) |
|---|---|---|
| Execution model | Inline, blocks PHP request | Async background worker |
| Timeout handling | Submission lost permanently | Retry with exponential backoff |
| Endpoint downtime | All submissions in window lost | Queue holds until endpoint recovers |
| Failure visibility | None | Per-attempt log with status codes |
| CF7 database dependency | CF7 doesn't save entries by default | Payload serialized to queue before attempt |
| Field name changes | Code breaks silently | UI mapping — update without deploying |
CF7 core does not save submission data to the database unless the Flamingo plugin is active. This makes reliable webhook delivery especially critical for CF7 integrations — if the raw PHP delivery fails, the submission data may exist nowhere.
Webhook Actions listens to CF7 submissions via its own wpcf7_before_send_mail listener. When a CF7 form is submitted, the plugin serializes the submission data into its own database queue before any HTTP delivery is attempted. The form confirmation renders to the user without waiting for any outbound HTTP call.
Webhook Actions' queue runner picks up the job on the next cycle — typically seconds on a properly configured site — and dispatches the webhook from a background process completely isolated from the original form request. When Action Scheduler is available (e.g. WooCommerce is active), WA uses it to trigger its queue runner reliably; otherwise it falls back to WP-Cron. If the endpoint fails, the job is rescheduled with exponential backoff and retried up to five times.
CF7 form submitted (browser POST)
│
▼
wpcf7_before_send_mail fires
│
▼
Webhook Actions: serialize payload → enqueue into WA queue
│
▼
CF7 confirmation message renders ◄── request ends here
│
(background)
▼
Action Scheduler worker dispatches webhook
│
├─ 2xx → logged as delivered
├─ 5xx / timeout → reschedule (exponential backoff)
├─ 4xx → permanent failure (logged, alert admin)
└─ max retries → dead-letter, manual retry available
Because the payload is serialized into Webhook Actions' own queue at hook time — before any delivery attempt — the submission data is safe even if WordPress crashes, the server reboots, or the endpoint is down for hours. When the endpoint comes back online, the queued jobs resume delivery.
Configuration is entirely UI-based. You select the Contact Form 7 trigger, optionally filter by form ID, enter the webhook URL, and configure field mappings. No code required. The full setup walkthrough is in the CF7 to Webhook example.
By default, Webhook Actions sends the complete CF7 get_posted_data() array as the webhook payload. This includes every field value keyed by its CF7 field name, plus CF7 internal keys like _wpcf7, _wpcf7_version, and _wpnonce. For most integrations you want a clean, selective payload — only the fields your downstream system needs, with human-readable key names.
Field mapping in Webhook Actions lets you define exactly which CF7 fields to include and what to name them in the payload. In the mapping UI, you enter a payload key (e.g., contact_name) and select the corresponding CF7 field name (e.g., your-name). The plugin builds the mapped payload at dispatch time.
CF7 checkbox fields store their values as arrays. A checkbox group named checkbox-menu with three checked options produces $data['checkbox-menu'] = ['Option A', 'Option C']. When you map this field in Webhook Actions, the array is serialized as a JSON array in the payload — ["Option A","Option C"]. Most downstream systems handle JSON arrays natively.
File upload fields ([file your-file] in CF7) store the uploaded file path in the submission data, not a URL. To send a usable URL in the webhook payload, you need to convert the path. Webhook Actions handles this conversion automatically — file upload fields are exposed as their full public URL in the mapping UI.
Testing a CF7 webhook integration requires verifying three things: the payload structure arriving at the endpoint, the behavior when the endpoint is unavailable, and the field mapping output for each field type in your form.
Use webhook.site as a temporary endpoint during development. It gives you a unique URL that logs every incoming request — headers, body, query string — in real time. Set this as your webhook URL in Webhook Actions, submit the CF7 form, and inspect the exact JSON body that arrives. Compare the keys and values against what your downstream system expects.
Temporarily set the webhook URL to a non-existent endpoint (e.g., https://localhost:9999/test) and submit the form. Check the Action Scheduler admin at wp-admin/admin.php?page=action-scheduler — you should see the job appear with status failed or pending (depending on timing). Verify the retry count increments on subsequent queue runner cycles. Then restore the real endpoint and confirm the job eventually delivers successfully.
Submit the form with known values in each field type (text, email, checkboxes, radio buttons, file upload if applicable) and compare the payload at webhook.site against the mapping you configured. Pay attention to checkbox arrays and any multi-value fields — these are the most common source of mapping surprises.
In the Webhook Actions admin, navigate to the delivery log for the CF7 trigger. Every delivery attempt shows the HTTP status code, response body excerpt, timestamp, and attempt number. A successful delivery shows status 200 (or whatever 2xx your endpoint returns). A failed attempt shows the status code and the beginning of the error response. This log is the primary debugging tool for any intermittent delivery issues.
wpcf7_before_send_mail and wpcf7_mail_sent action hooks that let developers add custom processing after a submission, but the webhook HTTP call must be built manually or via a plugin. Webhook Actions provides a no-code CF7 webhook trigger with field mapping, retry on failure, and delivery logs.
wpcf7_before_send_mail fires before Contact Form 7 sends the email notification — you can modify the submission data or abort the mail here. wpcf7_mail_sent fires after the email has been sent successfully. For webhook dispatch, wpcf7_before_send_mail is typically used because it gives you access to the WPCF7_Submission object at its most complete state.
WPCF7_Submission::get_instance() to get the current submission object, then call $submission->get_posted_data() to retrieve an associative array of field name to submitted value pairs. Field names correspond to the name attribute you set in the CF7 form editor — for example, [text* your-name] creates a field named 'your-name'.
wp_remote_post call fires synchronously during the form submission PHP request. If the endpoint is momentarily slow, returning a 5xx error, or under load, the call times out or fails — and since there is no queue or retry, the submission is permanently lost.