/ Article — Contact Form 7

Contact Form 7 to Webhook:
Send CF7 to Any Endpoint

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

  • CF7 has no native webhook feature — you either hook wpcf7_before_send_mail manually or use a plugin
  • Raw PHP delivery fires inline during the form request and silently drops entries when the endpoint is slow or down
  • Webhook Actions enqueues the CF7 payload into its own database queue, dispatches asynchronously via a background worker (triggered by Action Scheduler when available), maps fields by name, and retries on failure
/ Native CF7

What does Contact Form 7 do natively with webhook delivery?

Contact 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.

/ The Hook

What is the wpcf7_before_send_mail hook?

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.

Hook signature and accessing submission data
add_action( 'wpcf7_before_send_mail', function( $contact_form ) { $submission = WPCF7_Submission::get_instance(); if ( ! $submission ) { return; } // Associative array: field name => submitted value $data = $submission->get_posted_data(); // Access individual fields by their CF7 name attribute $name = isset( $data['your-name'] ) ? $data['your-name'] : ''; $email = isset( $data['your-email'] ) ? $data['your-email'] : ''; $message = isset( $data['your-message'] ) ? $data['your-message'] : ''; // Form metadata $form_id = $contact_form->id(); $form_title = $contact_form->title(); } );

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.

/ Basic Implementation

How do you send CF7 data to a webhook URL with raw PHP?

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.

functions.php — raw CF7 webhook delivery
add_action( 'wpcf7_before_send_mail', function( $contact_form ) { $submission = WPCF7_Submission::get_instance(); if ( ! $submission ) { return; } $data = $submission->get_posted_data(); $payload = [ 'form_title' => $contact_form->title(), 'name' => $data['your-name'] ?? '', 'email' => $data['your-email'] ?? '', 'message' => $data['your-message'] ?? '', 'submitted' => current_time( 'mysql', true ), ]; wp_remote_post( 'https://your-n8n.example.com/webhook/cf7', [ 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode( $payload ), 'timeout' => 10, ] ); // No retry. No queue. No log. Failure is silent and permanent. } );

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.

/ Production Reality

What breaks when you do it manually?

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

How does Webhook Actions handle CF7 webhook delivery?

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.

/ Field Mapping

How do you map CF7 form fields to webhook payload keys?

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.

Equivalent PHP for a field-mapped CF7 webhook payload
// This is what Webhook Actions builds from the mapping UI configuration. // No code to write — shown here for reference only. $payload = [ 'contact_name' => $data['your-name'] ?? '', 'email' => $data['your-email'] ?? '', 'message' => $data['your-message'] ?? '', 'phone' => $data['tel-541'] ?? '', 'form_id' => $contact_form->id(), 'submitted_at' => current_time( 'mysql', true ), ];

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

How do you test your CF7 webhook?

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.

1. Inspect the payload structure

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.

2. Test endpoint unavailability

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.

3. Verify field mapping

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.

4. Check delivery logs

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.

/ FAQ

Common questions

No. Contact Form 7 does not include a built-in webhook sender. It provides the 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.
Use 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'.
Yes. Webhook Actions connects Contact Form 7 to any webhook URL — including n8n, Zapier, Make, and custom endpoints — without writing code. You install the plugin, configure a CF7 trigger, paste the webhook URL, and optionally map specific CF7 fields to payload keys. The plugin handles async dispatch, retry on failure, and delivery logging automatically. See the CF7 to Webhook example for step-by-step instructions.
Intermittent CF7 webhook failures are almost always caused by inline delivery: the 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.

The fix is to move delivery out of the request into a queued, asynchronous background job with retry logic — which is what Webhook Actions does with its own database queue, using Action Scheduler (when available) to trigger the queue runner reliably.