WP Webhooks / Blog / WordPress integrations
Article · WordPress integrations

How to Send WPForms Entries to a Webhook

WPForms webhook setup: the Elite-only Webhooks addon, and the free wpforms_process_complete hook for when you need your own payload and headers.

8 min 2026-08-06
#WPForms#Forms

TL;DR: WPForms can send an entry to a webhook two ways, and they sit on opposite sides of a paywall.

  • No code: the Webhooks addon adds a request panel to the form builder — but it needs an Elite licence.
  • With code: hook wpforms_process_complete, which fires at the very end of a successful submission and works on any tier, including Lite.
  • The hook hands you $fields, $entry, $form_data and $entry_id — everything needed to build your own payload.
  • $entry_id is 0 on Lite or with entry storage off, so never use it as your idempotency key.
  • Either route sends the request inline unless you queue it, so a slow endpoint becomes slow form submissions.

/ Overview

Does WPForms have a webhook?

Yes, and there are two entirely separate routes to one. The first is the Webhooks addon, which puts a request builder in the form editor and needs no PHP. The second is wpforms_process_complete, an action WPForms fires at the end of every successful submission, which you handle in a snippet.

Which one you can use is mostly a licensing question. WPForms documents the Webhooks addon as requiring an Elite licence level, the top tier — Basic, Plus and Pro do not include it. The action hook is part of the plugin's own processing code, so it is available on every tier including the free WPForms Lite.

That makes the decision unusually clear compared with other form plugins. If you are already on Elite and your endpoint is happy with the payload WPForms produces, use the addon. In every other case the hook is the route, and it is not much work.

The WPForms Webhooks addon compared with the wpforms_process_complete hookA visitor submits a WPForms form. WPForms validates the fields, saves the entry and sends its email notifications. On an Elite licence the Webhooks addon then sends the request configured in the form builder, using the method, format and headers set there. Independently of licence tier, WPForms fires the wpforms_process_complete action at the very end of successful processing, where a PHP callback can build its own payload and send or enqueue it. Both routes reach an external endpoint, but only the code route lets you reshape the body or hand the delivery to a background queue.

method + format + headers
set in the builder

your PHP callback

visitor submits the form

WPForms validates fields

entry saved + notifications sent

Webhooks addon
(Elite licence)

wpforms_process_complete fires
( $fields, $entry, $form_data, $entry_id )

external endpoint

build payload,
send or enqueue

FIG 01 — Two routes from a WPForms entry to your endpoint

/ Built-in addon

What does the Webhooks addon actually send?

Whatever you tell it to, within a fixed set of options. The addon exposes five request methods — GET, POST, PUT, PATCH and DELETE — and two body formats: JSON, sent as application/json, or form-encoded, sent as application/x-www-form-urlencoded. You map form fields to request keys in the builder rather than accepting a fixed payload shape, which is the main thing it does better than the built-in webhook actions in most competing form plugins.

Two options matter for talking to a real API. Custom request headers let you pass an API key, which is the difference between a webhook you can point at a service and one you can only point at a catch-all URL. A Secret field generates a per-request hash sent as a header, so the receiver can confirm the request came from your site rather than anyone who learned the URL.

The limits are the shape of the payload and the shape of the failure. You get a flat mapping of fields to keys, so an endpoint expecting nested objects or a computed value needs a translation layer somewhere. And a request that fails is a request that failed — the addon is a sender, not a delivery system with its own retry schedule.

/ The hook

What is wpforms_process_complete?

It is the action that runs at the very end of successful entry processing. WPForms documents the signature as four parameters:

ParameterTypeWhat it holds
$fieldsarraySanitised entry field values and properties, keyed by field ID.
$entryarrayThe original $_POST payload for the submission.
$form_dataarrayThe processed form settings — including the form ID and title.
$entry_idintThe saved entry ID, or 0 when entry storage is off or the site runs Lite.

The timing is the important part. It fires only when the submission passed validation, and only after the entry has been written and notification emails have gone out. So by the time your callback runs, the entry is real and the user is already being redirected or shown the confirmation. Nothing you do here can reject the submission — for that you want the earlier wpforms_process action, which is where validation errors belong.

wpforms_process_complete runs at the very end of successful form entry processing — after the entry is saved and the notifications are sent. — WPForms developer documentation

/ Implementation

How do you post a WPForms entry to your own API?

Bind a callback, guard it to one form, build the body you want, and send it with wp_remote_post. The form guard is not optional — without it every form on the site posts to your endpoint, including the contact form you forgot about.

PHP — send a WPForms entry to an endpoint

add_action( 'wpforms_process_complete', function( $fields, $entry, $form_data, $entry_id ) {

    // Only the form we care about.
    if ( 42 !== (int) $form_data['id'] ) {
        return;
    }

    // $fields is keyed by field ID; 'name' is the label, 'value' the answer.
    $payload = [
        'form'     => $form_data['settings']['form_title'],
        'entry_id' => $entry_id ?: null,
        'answers'  => wp_list_pluck( $fields, 'value', 'name' ),
    ];

    wp_remote_post( 'https://example.com/hooks/wpforms', [
        'timeout'  => 5,
        'headers'  => [
            'Content-Type'  => 'application/json',
            'Authorization' => 'Bearer ' . getenv( 'HOOK_TOKEN' ),
        ],
        'body'     => wp_json_encode( $payload ),
        'blocking' => true,
    ] );

}, 10, 4 );

Two details in there are easy to get wrong. The 4 at the end is the argument count — omit it and PHP hands your callback one parameter, so $form_data is undefined and the guard fails. And $fields is keyed by numeric field ID, not by label, which is why the example flattens it with wp_list_pluck before sending. Field IDs are stable across label edits; that is a feature, not an inconvenience.

/ Entry IDs

Why does $entry_id come back as 0?

Because nothing was saved. WPForms returns 0 when entry storage is disabled for that form or when the site is running WPForms Lite, which does not store entries at all. The submission still processed and your callback still ran — there is simply no row to point at.

This matters more than it looks. A receiving endpoint usually wants an idempotency key so a replayed request does not create a duplicate record downstream, and $entry_id is the obvious candidate. On a Lite site every request would arrive with the same key of 0, and a well-behaved consumer would discard all but the first. Generate your own identifier instead — a UUID minted in the callback, or a hash of the form ID and the submission timestamp — and treat $entry_id as a convenience link back to wp-admin when it happens to be non-zero.

/ Delivery

What happens when the endpoint is slow or down?

The visitor waits, and then the data is gone. A blocking wp_remote_post inside the hook adds the endpoint's full response time to the submission, and the default WordPress HTTP timeout is 5 seconds. A receiver having a bad afternoon at 4 seconds per request means every person filling in your form waits an extra 4 seconds after pressing submit for no visible reason.

The failure mode is worse than the latency. If the request times out or returns a 500, the visitor still sees the success message — the entry saved fine, only the delivery failed — and nothing anywhere records that the lead never reached your CRM. This is the same trap that catches Elementor form deliveries and Contact Form 7 submissions, and the fix is the same in all three: do not send in the request, enqueue.

Behaviourwp_remote_post in the hookQueued delivery
Submission timeVisitor waits for the endpointReturns immediately
Endpoint downRequest lost, no recordStays queued, retried later
RetriesNoneExponential backoff until a cap
VisibilityNothing to inspectPer-attempt log with request and response

/ Debugging

How do you debug a webhook that never arrives?

Work outwards from the hook. Most of these turn out to be the guard or the argument count rather than anything to do with HTTP.

  1. Confirm the hook runs at all. Drop an error_log( 'process_complete fired' ) as the first line of the callback and submit the form. No line in the log means the submission failed validation, or the snippet is not loaded.
  2. Confirm the form guard matches. Log $form_data['id'] and compare it with the ID in the form's edit URL. Comparing a string to an integer without a cast is the classic version of this bug.
  3. Check the argument count. If $form_data is null, the fourth argument to add_action is missing or wrong.
  4. Log the response, not just the request. wp_remote_post returns either a WP_Error or an array — log wp_remote_retrieve_response_code() so a 401 is not mistaken for a request that never left.
  5. Rule out the outbound connection. A host that blocks external HTTP, or a firewall that drops the destination, produces a WP_Error with a connection message and no server-side trace at the receiver.

If step 5 is where it fails, that is an infrastructure problem, not a code one, and no amount of rewriting the callback will fix it.

/Footnotes
¹ Signature and parameter descriptions from the WPForms developer reference for wpforms_process_complete.
² Licence requirement, request methods, body formats, custom headers and the Secret option from the WPForms Webhooks addon documentation.
FAQ

Things engineers always ask.

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

Does WPForms have a webhook? +
Yes, two ways. The Webhooks addon adds a no-code webhook panel to the form builder, but it requires an Elite licence. On any tier you can hook wpforms_process_complete in PHP and send the request yourself, which is also the only route that lets you shape the payload exactly.
Which WPForms licence do I need for webhooks? +
Elite. WPForms documents the Webhooks addon as an Elite-level addon, so Lite, Basic, Plus and Pro licences do not have it. The wpforms_process_complete action is part of the plugin itself rather than an addon, so a code-based webhook works on any tier including WPForms Lite.
What is wpforms_process_complete? +
It is the action WPForms fires at the very end of a successful entry submission, after the entry has been saved and notification emails have been sent. Its signature is do_action('wpforms_process_complete', $fields, $entry, $form_data, $entry_id), and it does not fire when validation fails.
Why is $entry_id always 0 in wpforms_process_complete? +
Because entry storage is off. WPForms documents $entry_id as returning 0 when entry storage is disabled or the site is running WPForms Lite. Use it for a link back to the entry when it is non-zero, but never make it the idempotency key your endpoint deduplicates on.
What request methods and formats does the WPForms Webhooks addon support? +
GET, POST, PUT, PATCH and DELETE, with the body sent either as JSON (application/json) or as form-encoded data (application/x-www-form-urlencoded). You can add custom request headers for an API key, and a Secret option adds a per-request hash header the receiver can check.
Ready

Your next automation is
one sentence away.

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