WP Webhooks / Blog / WordPress integrations
Article · WordPress integrations

Posting WordPress Form Submissions Into a Slack Channel

Send WordPress form entries to Slack: completion hooks for CF7, Gravity Forms and WPForms, Block Kit payloads, the 1 msg/s limit and why to queue it.

8 min 2026-08-15
#slack#forms#integrations

TL;DR:

  • Three form plugins, three completion hooks — wpcf7_mail_sent, gform_after_submission, wpforms_process_complete — all feeding one payload builder.
  • A Slack incoming webhook is a plain POST of JSON. Minimum body is { "text": "..." }; success is HTTP 200 with the body ok.
  • The webhook URL is the credential. Slack actively hunts leaked ones and revokes them.
  • Rate limit is roughly one message per second with short bursts tolerated; beyond that you get 429 and a Retry-After.
  • Incoming webhooks cannot edit or delete a message once posted.

/ Overview

How do you post WordPress form entries to Slack?

Catch the form plugin's completion hook, build a JSON message, and POST it to the incoming webhook URL Slack gave you. There is no authentication step beyond the URL itself and no SDK worth installing — the whole integration is one HTTP request with a Content-type: application/json header.¹

What varies between form plugins is only where the data comes from. Everything after that is shared, which is why the payload builder should be one function rather than three copies.

FIG 01 — Form entry to Slack channel, and where it blocks

/ Hooks

Which hook fires when each form plugin finishes?

All three fire after a successful submission, but they hand you very different things — and only one of them gives you the data directly.

PluginHookWhat the callback receives
Contact Form 7wpcf7_mail_sentThe WPCF7_ContactForm object only — read values via WPCF7_Submission
Gravity Formsgform_after_submission$entry and $form — the entry holds values keyed by field ID
WPFormswpforms_process_complete$fields, $entry, $form_data, $entry_id — four arguments

WPForms is the one with a trap worth knowing: its fourth argument, $entry_id, returns 0 when entry storage is disabled or the site is running WPForms Lite.² Code that builds a "view this entry" link from it produces a dead link on every Lite install, and it will look fine on your Pro development site.

PHP — three sources, one builder

// Contact Form 7
add_action( 'wpcf7_mail_sent', function( $contact_form ) {
    $s = WPCF7_Submission::get_instance();
    if ( $s ) {
        my_notify_slack( $contact_form->title(), $s->get_posted_data() );
    }
} );

// Gravity Forms
add_action( 'gform_after_submission', function( $entry, $form ) {
    my_notify_slack( $form['title'], $entry );
}, 10, 2 );

// WPForms — note the argument count of 4
add_action( 'wpforms_process_complete', function( $fields, $entry, $form_data, $entry_id ) {
    my_notify_slack( $form_data['settings']['form_title'], $fields );
}, 10, 4 );

/ The request

What does a Slack incoming webhook expect?

A POST with Content-type: application/json and a body containing at least a text field. Success is HTTP 200 with the literal response body ok — not JSON, just those two characters, which is worth knowing because a naive json_decode() of the response returns null on a perfectly successful send.¹

PHP — posting to the webhook

function my_notify_slack( $form_title, array $values ) {

    $lines = [];
    foreach ( $values as $k => $v ) {
        if ( is_array( $v ) ) {
            $v = implode( ', ', $v );
        }
        $lines[] = sprintf( '*%s:* %s', $k, wp_strip_all_tags( (string) $v ) );
    }

    $body = [
        'text' => sprintf( "New submission: %s\n%s", $form_title, implode( "\n", $lines ) ),
    ];

    $res = wp_remote_post( MY_SLACK_WEBHOOK_URL, [
        'timeout' => 10,
        'headers' => [ 'Content-Type' => 'application/json' ],
        'body'    => wp_json_encode( $body ),
    ] );

    // success is the plain string "ok", not JSON
    return ! is_wp_error( $res )
        && 'ok' === trim( wp_remote_retrieve_body( $res ) );
}

Note wp_strip_all_tags() on every value. Form input reaches Slack unescaped otherwise, and Slack's mrkdwn parser treats *, _ and <> as formatting — a message body containing <https://evil.example|click here> renders as a disguised link in your channel.

/ Formatting

When should you use Block Kit instead of text?

Use text until the message needs structure, then move to blocks. A plain-text message is one field and always renders; Block Kit gives you headers, two-column field lists and buttons, at the cost of a much larger payload that fails validation as a unit.

One practical rule: keep text populated even when sending blocks. It is what Slack shows in the notification preview and in clients that cannot render the blocks, and a message with blocks but no text arrives as a silent, empty-looking notification.

The webhook URL is the entire authentication story. Anyone who has it can post to your channel as your app, forever, from anywhere.

/ Limits

What are Slack's rate limits for incoming webhooks?

Incoming webhooks and chat.postMessage sit in Slack's Special tier: one message per second, with short bursts above that tolerated. Sustained excess returns HTTP 429 with a Retry-After header in seconds, and Slack warns that continued abuse risks the app being disconnected or disabled outright.³

One per second sounds ample until a bulk action fans out. Import 500 entries and notify on each and you need 500 seconds — over eight minutes — of continuous posting, during which every other notification from the same app is competing for the same budget. Batch the summary instead of sending 500 messages nobody will read.

/ Gaps

What does Slack not protect you from?

  • A leaked URL is a live credential. Slack states it actively searches out and revokes leaked webhook URLs — which means a URL committed to a public repository can be revoked without warning, and your notifications simply stop.
  • No edit, no delete. Incoming webhooks cannot modify a message after posting. If a submission contained something that should not have been broadcast, the only remedy is manual.
  • No delivery guarantee on your side. Slack returning ok means Slack accepted it. If your request never left because the site was mid-deploy, nothing anywhere records that a notification was owed.
  • Everything you send is readable by the whole channel. A form that collects a phone number or an address will put it in front of every member — including guests. Send a link and a name, not the payload.

/ Delivery

Should the send be inline or queued?

Queued, once notifications matter. Inline is defensible for a low-traffic contact form: the failure mode is a missed Slack message, not lost data, because the entry is already stored by the form plugin. That is a genuinely different risk profile from the Airtable and Notion cases, where the third party holds the only copy.

It stops being defensible as soon as the message is the record — an alert nobody else stores, or a notification an on-call process depends on. Then a 10-second timeout inside a form submission is both a visitor-facing delay and a single point of loss.

ConcernInline postQueued delivery
Visitor waitUp to the full timeoutNone
Slack 429Message droppedRetried after Retry-After
Bulk actionsTrips the 1/s limit immediatelyPaced automatically
Proof it was sentNonePer-attempt log with the response

/ Secrets

How do you keep the webhook URL out of your code?

Put it in wp-config.php as a constant, or in an encrypted store — never in a theme file, never in a plugin you commit, and never in a post meta field that an editor can read. The URL grants posting rights with no expiry and no scope, so it deserves the same handling as a database password.

If the site is in version control, add a guard that fails loudly when the constant is missing, rather than defaulting to a hard-coded fallback. A missing-constant fatal on deploy is a five-minute fix; a fallback URL that quietly posts a client's form entries into your own test channel is not.

Try it without writing the plumbing. Open the live preview — a full WordPress with Webhook Actions already configured, no signup — or install the free plugin and point a form trigger at your Slack URL.

/Footnotes
¹ POST format, the JSON body shape, the plain ok success response and the note that Slack revokes leaked webhook URLs: Sending messages using incoming webhooks.
² The four-argument signature and the $entry_id returning 0 on WPForms Lite: wpforms_process_complete.
³ The Special tier, one message per second with burst tolerance, 429 and Retry-After: Slack Web API rate limits.
WordPress HTTP helper used above: wp_remote_post().
FAQ

Things engineers always ask.

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

How do you send a WordPress form submission to Slack? +
Hook the form plugin completion action — wpcf7_mail_sent, gform_after_submission or wpforms_process_complete — build a JSON body containing at least a text field, and POST it to your Slack incoming webhook URL with Content-type application/json.
What does a Slack incoming webhook return on success? +
HTTP 200 with the plain string ok as the response body. It is not JSON, so decoding the response as JSON returns null even on a completely successful send.
What is the rate limit for Slack incoming webhooks? +
Roughly one message per second, in Slack Special tier, with short bursts above that tolerated. Sustained excess returns HTTP 429 with a Retry-After header, and Slack warns that continued abuse can get an app disconnected or disabled.
Should I use text or Block Kit blocks? +
Use text until the message needs structure such as headers, field lists or buttons. Even when sending blocks, keep the text field populated — it is what Slack shows in the notification preview and in clients that cannot render blocks.
Is a Slack webhook URL a secret? +
Yes. The URL is the entire credential — anyone holding it can post to that channel as your app with no expiry. Store it in wp-config.php or an encrypted store, never in committed code. Slack actively searches for leaked webhook URLs and revokes them.
Ready

Your next automation is
one sentence away.

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