WP Webhooks / Blog / WordPress integrations
Article · WordPress integrations

wpcf7_mail_sent Hook: Send CF7 Data to a Webhook

wpcf7_mail_sent fires after Contact Form 7 sends its email. Hook signature, accessing submitted fields, webhook dispatch, reliability risks, and plugin comparison.

8 min 2026-05-30
#contact-form-7#wordpress#webhooks

TL;DR

  • wpcf7_mail_sent fires after CF7 successfully sends its notification email — use it when you need confirmed delivery before dispatching a webhook
  • Access field values via WPCF7_Submission::get_instance()->get_posted_data()
  • Calling wp_remote_post inside the hook is synchronous — it blocks PHP until your endpoint responds
  • Webhook Actions ships a built-in CF7 integration with async queue, retry, and delivery logs — no custom PHP needed

/ What is wpcf7_mail_sent

What is wpcf7_mail_sent and when does it fire?

wpcf7_mail_sent is a WordPress action hook provided by Contact Form 7 (CF7) — with over 10 million active installs¹, the most widely deployed form plugin in the WordPress ecosystem. The hook fires at the end of CF7's form processing pipeline, specifically after the plugin has successfully delivered its configured notification email.

The practical implication: if you hook into wpcf7_mail_sent, you know the form passed validation, cleared spam checks, and that CF7's own mailer completed without error. This makes it the right attachment point for downstream automations — CRM entries, Slack notifications, and webhook dispatches — where you want confirmation the form fully succeeded before triggering side effects.

Visitor submits form (POST)
      │
      ▼
 CF7 validates fields
 (required fields, email format, etc.)
      │ validation passes
      ▼
 Spam check (Akismet / CAPTCHA)
      │ passes
      ▼
 wpcf7_before_send_mail fires ◄─ can set $abort = true here
      │ not aborted
      ▼
 CF7 sends notification email
      │
      ├─ mail success ──► wpcf7_mail_sent fires ◄─ your webhook here
      │
      └─ mail failure ──► wpcf7_mail_failed fires
FIG 01 — CF7 form submission and hook lifecycle

/ Before vs After

How does wpcf7_mail_sent differ from wpcf7_before_send_mail?

Contact Form 7 exposes two hooks around its mail-sending step. wpcf7_before_send_mail fires immediately before CF7 attempts to send the email and receives a reference to the $abort flag — setting it to true prevents both the email send and any wpcf7_mail_sent callback. This makes wpcf7_before_send_mail the right hook for altering payload data or implementing custom spam filtering.

wpcf7_mail_sent fires only after the mail send completes successfully. You cannot abort or modify the submission at this point — the hook is purely for reacting to a confirmed submission. Use wpcf7_mail_sent when your downstream system should only receive data that CF7 itself treated as fully processed.

A third hook, wpcf7_submit, fires after validation but before the mail step. If you need to capture every validated submission regardless of email delivery status, wpcf7_submit is the hook to use — but for webhook dispatch, the later wpcf7_mail_sent is usually preferable because it filters out aborted or failed submissions automatically.

/ Hook parameters

What parameters does wpcf7_mail_sent pass to your callback?

The hook passes a single argument: $contact_form, an instance of WPCF7_ContactForm. This object exposes the form configuration — its ID, title, and properties — but not the submitted field values. Submitted values live on the submission object, which is accessed separately.

PHP — hook registration

add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    // $contact_form is WPCF7_ContactForm
    $form_id   = $contact_form->id();       // integer
    $form_name = $contact_form->title();    // string — the form name from CF7 editor

    // Submitted values are NOT on $contact_form — see next section
} );

/ Accessing field values

How do you access submitted field values inside wpcf7_mail_sent?

Submitted data is available on the WPCF7_Submission singleton. Call WPCF7_Submission::get_instance() — this returns the submission object for the current request, or null outside a CF7 processing context. Always null-check before proceeding.

The get_posted_data() method returns an associative array keyed by the name attributes set in the CF7 form editor (the values inside square brackets in CF7 shortcodes, e.g. [text your-name]). All values are run through CF7's sanitization pipeline before being available here.

PHP — reading submitted field values

add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    $submission = WPCF7_Submission::get_instance();
    if ( ! $submission ) {
        return;
    }

    $data = $submission->get_posted_data();
    // $data is an associative array:
    // [ 'your-name' => 'Jane Smith', 'your-email' => '[email protected]', ... ]

    $payload = [
        'form_id'   => $contact_form->id(),
        'form_name' => $contact_form->title(),
        'name'      => $data['your-name']    ?? '',
        'email'     => $data['your-email']   ?? '',
        'message'   => $data['your-message'] ?? '',
    ];
} );

/ Webhook dispatch

How do you dispatch a webhook from wpcf7_mail_sent?

Once you have the payload array, pass it to wp_remote_post with a JSON body. The implementation is a direct HTTP call: serialise the payload with wp_json_encode, set Content-Type: application/json, and send to your endpoint URL.

PHP — wp_remote_post dispatch inside wpcf7_mail_sent

add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    $submission = WPCF7_Submission::get_instance();
    if ( ! $submission ) {
        return;
    }

    $data    = $submission->get_posted_data();
    $payload = [
        'form_id' => $contact_form->id(),
        'name'    => $data['your-name']    ?? '',
        'email'   => $data['your-email']   ?? '',
        'message' => $data['your-message'] ?? '',
    ];

    wp_remote_post( 'https://your-endpoint.com/webhook', [
        'headers'     => [ 'Content-Type' => 'application/json' ],
        'body'        => wp_json_encode( $payload ),
        'timeout'     => 5,
        'blocking'    => true,   // ← PHP waits here
        'data_format' => 'body',
    ] );
} );

/ Reliability risks

What are the reliability risks of synchronous dispatch inside a hook?

The code above works — but it is synchronous. When PHP executes wp_remote_post with 'blocking' => true, the process stalls until your webhook endpoint sends a response or the timeout elapses (5 seconds in the example). During that window, the user's browser is waiting for the CF7 success response, the PHP-FPM worker is occupied, and any slow or unavailable endpoint directly degrades your form's perceived performance.

There is also no retry. If your endpoint returns a 5xx error or times out, the delivery is silently lost — no log entry, no re-attempt, no alert. On high-traffic forms this adds up quickly: a downstream endpoint that experiences 2% transient failures across 500 daily submissions means 10 missed integrations per day with no visibility.

Calling wp_remote_post synchronously inside a form hook means every visitor who submits your form waits for your webhook endpoint — not just for WordPress.

/ Webhook Actions

How does Webhook Actions handle CF7 webhook delivery without custom code?

The Webhook Actions plugin includes a built-in Contact Form 7 integration. When CF7 is active alongside Webhook Actions, CF7 form submissions appear as native trigger types in the plugin's admin UI. You select the form, map fields to your payload using the visual mapper, and configure the endpoint URL — no PHP code required.

Delivery is handled asynchronously: Webhook Actions enqueues each submission via Action Scheduler when available, which means the form response returns immediately and the HTTP dispatch happens in the next queue cycle — typically within seconds but always outside the visitor's request. Failed deliveries retry automatically with exponential backoff (1 min → 2 min → 4 min → 8 min, capped at 1 hour, default 5 attempts) and are logged with the full request body and HTTP response for inspection in wp-admin.

CapabilityRaw wpcf7_mail_sent + wp_remote_postWebhook Actions (built-in CF7)
SetupCustom PHP in functions.php or pluginVisual admin UI — no code
DeliverySynchronous — blocks form responseAsync via Action Scheduler queue
RetryNone — one attempt onlyExponential backoff, 5 attempts
Delivery logNone — failures are silentPer-attempt log with request and response in wp-admin
Field mappingManual array construction in PHPGUI mapper — rename, restructure, exclude fields
Multiple webhooksOne add_action per endpointUnlimited webhooks per form trigger

/ Testing and monitoring

How do you test and monitor CF7 webhook delivery in production?

The quickest way to validate the raw wpcf7_mail_sent approach is to point the endpoint URL at a request inspector like webhook.site — a temporary URL that logs every inbound request with full headers and body. Submit the form, then inspect the payload on webhook.site to confirm field values and structure before switching to the real destination.

For production monitoring without a plugin, is_wp_error on the wp_remote_post return value surfaces transport errors (timeouts, DNS failures), and wp_remote_retrieve_response_code gives the HTTP status. Log both to error_log or a custom table for visibility. With Webhook Actions, delivery logs are built-in: every attempt records the timestamp, HTTP status code, full response body, and retry count — visible from the Webhook Actions → Logs screen.

The Contact Form 7 to Webhook overview covers the full Webhook Actions setup flow including test delivery from the plugin admin.

/Footnotes
¹ Contact Form 7 active install count from the WordPress.org plugin directory.
² Contact Form 7 documentation — official hook and API reference.
³ Action Scheduler usage guide — async job scheduling for WordPress.
FAQ

Common questions always ask.

Don't see yours? Open an issue on GitHub or check the full reference in the API docs.

What is the difference between wpcf7_mail_sent and wpcf7_before_send_mail? +
wpcf7_before_send_mail fires before CF7 sends its notification email and can abort the send by setting $abort to true. wpcf7_mail_sent fires after the email has been sent successfully. For webhook dispatch where you want confirmation the form was fully processed, wpcf7_mail_sent is the right hook.
How do you access Contact Form 7 field values inside wpcf7_mail_sent? +
Call WPCF7_Submission::get_instance() to retrieve the active submission object, then call $submission->get_posted_data() to get an associative array of field names to submitted values. Field names match the name attributes in the CF7 form editor.
Does wpcf7_mail_sent fire if the CF7 email fails to send? +
No. wpcf7_mail_sent fires only after a successful mail delivery. If you need to catch all valid submissions regardless of email status, use wpcf7_submit, which fires after validation passes but before the mail send attempt.
Is it safe to call wp_remote_post inside wpcf7_mail_sent? +
It works but blocks PHP execution until the remote endpoint responds. On a slow or unavailable endpoint this delays the success response seen by the visitor. The safer pattern is to enqueue the delivery via Action Scheduler or use Webhook Actions, which handles async delivery with automatic retry.
Does Webhook Actions require a separate CF7 webhook plugin? +
No. Webhook Actions has a built-in CF7 integration. When Contact Form 7 is active, submissions appear as triggers in the webhook configuration screen with structured, pre-mapped field payloads — no additional plugin or custom PHP code needed.
Ready

Stop losing webhooks.
Start logging them.

$ wp plugin install flowsystems-webhook-actions --activate