Integrations
BotShield on WordPress
BotShield works on any WordPress site with a few lines in functions.php: enqueue the SDK, render the widget inside your form, and verify the submission on the server. There are no cookies to disclose and nothing to configure in the dashboard beyond creating a project.
There is no BotShield plugin in the WordPress directory yet. This guide is the manual integration, which works today on any theme. A plugin is in progress; until it is published and reviewed, use the code below.
Before you start
Create a project in the dashboard and copy its API key. The free plan covers 1,000 solves a month and two domains, which is enough for most single-site contact forms — see pricing.
Put the key somewhere that is not a theme file, for example a wp-config.php constant:
define( 'BOTSHIELD_SECRET_KEY', 'sc_live_YOUR_API_KEY' );A theme edit is overwritten by the next theme update. For anything you intend to keep, put these snippets in a small site-specific plugin or a child theme.
1. Load the SDK
Use wp_enqueue_script rather than pasting a script tag into the template, so WordPress controls load order and caching.
<?php
// functions.php — load the BotShield SDK on the front end.
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'botshield',
'https://botshield.dev/sdk.min.js',
array(),
'0.2.5-1',
true // load in the footer
);
} );2. Render the widget
A shortcode is the least invasive way to place the widget, because it works in the block editor, in Contact Form 7 templates and in raw HTML widgets alike.
<?php
// functions.php — a [botshield] shortcode you can drop into any form.
add_shortcode( 'botshield', function () {
$key = esc_attr( get_option( 'botshield_site_key' ) );
return '<div id="botshield" data-key="' . $key . '"></div>';
} );3. Verify on the server
This is the step that actually provides the protection. Without it, a bot can post straight to admin-post.php and never load your page at all.
<?php
// functions.php — verify before you trust a submission.
function botshield_verify( array $post ) {
$response = wp_remote_post( 'https://botshield.dev/api/v1/verify', array(
'headers' => array( 'Content-Type' => 'application/json' ),
'timeout' => 5,
'body' => wp_json_encode( array(
// Store the secret key with wp-config.php constants or an
// options row — never hard-code it in a theme file.
'key' => BOTSHIELD_SECRET_KEY,
'captcha_challenge' => $post['captcha_challenge'] ?? '',
'captcha_nonce' => $post['captcha_nonce'] ?? '',
'captcha_signature' => $post['captcha_signature'] ?? '',
) ),
) );
if ( is_wp_error( $response ) ) {
// Fail closed: if we cannot reach the API, do not accept the form.
return false;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
return ! empty( $body['success'] );
}Contact Form 7
<?php
// Contact Form 7: reject the submission if the captcha did not verify.
add_filter( 'wpcf7_validate', function ( $result, $tags ) {
if ( ! botshield_verify( $_POST ) ) {
$result->invalidate(
$tags[0],
__( 'Could not verify that you are human. Please try again.', 'botshield' )
);
}
return $result;
}, 10, 2 );
// Put [botshield] in the form template, next to your submit button.WPForms
<?php
// WPForms: same check, on its own validation hook.
add_action( 'wpforms_process', function ( $fields, $entry, $form_data ) {
if ( ! botshield_verify( $_POST ) ) {
wpforms()->process->errors[ $form_data['id'] ]['header'] =
esc_html__( 'Could not verify that you are human. Please try again.', 'botshield' );
}
}, 10, 3 );Comment forms
<?php
// Comment form: render the widget, then verify on submit.
add_action( 'comment_form_after_fields', function () {
echo do_shortcode( '[botshield]' );
} );
add_filter( 'preprocess_comment', function ( $commentdata ) {
if ( ! is_user_logged_in() && ! botshield_verify( $_POST ) ) {
wp_die( esc_html__( 'Could not verify that you are human.', 'botshield' ) );
}
return $commentdata;
} );Troubleshooting
- The widget never appears — check the browser console for
[BotShield] No data-key found, which means the shortcode rendered with an empty key. - Submissions are always rejected — the three hidden fields are only added to the closest
formelement. If your form plugin submits over AJAX from somewhere else, read the values from thebotshield:solvedevent instead. - It works locally but not in production — add the production domain to the project’s protected domains in the dashboard.
- A caching plugin serves a stale challenge — challenges expire after five minutes. Exclude the page from full HTML caching, or render the widget container only, since the challenge is fetched by the SDK at runtime rather than baked into the HTML.
Full reference for the attributes, events and the verify API is in the documentation.