This page documents the extension points SmartDingo Form Tracker exposes to code: filters, actions,
the admin-ajax.php endpoints, the leads database table, the lead-endpoint security model, the
front-end config global, and the selector-scan mode. Everything here is verified against the plugin
source.
Note There is no REST API. The plugin does not register any
register_rest_route/rest_api_initroutes. All server communication goes through classicadmin-ajax.phpactions
(listed below). Do not build integrations against a REST namespace — none exists.
sdft_validate_lead_submission (Starter)The most useful hook. It runs inside the lead-logging endpoint just before a lead is de-duplicated
and written to the database, after all built-in security checks have already passed. Use it to add
your own gate — a CAPTCHA/Turnstile check, a WAF or fraud-API lookup, or verification of a hidden
token rendered by your specific form plugin.
Signature: apply_filters( 'sdft_validate_lead_submission', true, $data, $_POST )
$data — the sanitized, decoded lead payload (keys include tracking, contact, visitor_id,form_name, form_page, capture_method).$_POST — the raw POST array from the submission.Return true (or the unchanged value) to accept the lead. Return a WP_Error to reject it;
its message is sent back to the browser and nothing is stored.
add_filter( 'sdft_validate_lead_submission', function ( $valid, $data, $post ) {
// Example: require a hidden token your own form plugin renders server-side.
if ( empty( $post['my_form_token'] ) || ! my_verify_token( $post['my_form_token'] ) ) {
return new WP_Error( 'sdft_rejected', 'Lead failed token verification.' );
}
return $valid; // Accept.
}, 10, 3 );Note This filter only ever fires when lead tracking is licensed and enabled, so it is a
Starter/Pro concern. It is the intended place to compensate for the endpoint’s inherent limitation:
a browser-only tracker cannot cryptographically prove that a real form actually submitted.
| Action | When it fires | Typical use |
|---|---|---|
sdft_fs_loaded | Once, right after the Freemius SDK is initialized in the main plugin file | Safely run code that calls sdft_fs() (e.g. read the current plan) |
sdft_leads_cleanup (Starter) | Daily WP-Cron event; deletes leads older than the configured retention | Force retention cleanup manually |
sdft_leads_cleanup is scheduled with wp_schedule_event( …, 'daily', … ) on activation and runsSDFT_Leads_DB::run_cleanup(), which deletes rows older than sdft_lead_retention_months (a value
of 0 means “never delete”, so cleanup is a no-op). You can trigger it by hand:
wp cron event run sdft_leads_cleanup…or in PHP with do_action( 'sdft_leads_cleanup' );.
All endpoints are classic admin-ajax.php actions. Two are public (they register bothwp_ajax_ and wp_ajax_nopriv_); the rest are admin-only and require the manage_options
capability plus a nonce.
| Action | Access | Purpose |
|---|---|---|
sdft_log_lead (Starter) | Public | Receive and store a lead from the front-end tracker |
sdft_lead_token (Starter) | Public | Issue a fresh nonce + one-time token (cache-proof credentials) |
sdft_search_pages | Admin | Search pages/posts by title (page scanner picker) |
sdft_export_settings (Starter) | Admin | Export plugin settings as JSON |
sdft_import_settings (Starter) | Admin | Import settings from an uploaded JSON file |
sdft_export_leads_csv (Starter) | Admin | Stream the leads CSV download |
sdft_save_dash_prefs (Starter) | Admin | Save the current user’s dashboard widget preferences |
Warning The two public endpoints are the only unauthenticated surface. They are hardened as
described under Lead endpoint security model — do not remove or
weaken those checks in a fork.
wp_sdft_leads tableLeads are stored in {$wpdb->prefix}sdft_leads. The table denormalizes the most-queried attributes
into their own columns (so filtering, sorting, and dashboard aggregation stay fast) and keeps the
full captured payload in two JSON blobs.
| Column | Type | Indexed | Notes |
|---|---|---|---|
id | BIGINT UNSIGNED | PK | Auto-increment |
visitor_id | VARCHAR(64) | ✅ | Anonymous per-visitor id |
lead_email | VARCHAR(255) | — | |
lead_name | VARCHAR(255) | — | |
lead_phone | VARCHAR(50) | — | |
utm_source | VARCHAR(255) | ✅ | |
utm_medium | VARCHAR(255) | ✅ | |
utm_campaign | VARCHAR(255) | ✅ | |
first_utm_source | VARCHAR(255) | — | First-touch |
first_utm_medium | VARCHAR(255) | — | First-touch |
first_utm_campaign | VARCHAR(255) | — | First-touch |
referrer_category | VARCHAR(50) | ✅ | |
landing_page | VARCHAR(255) | — | |
device_type | VARCHAR(50) | — | |
form_name | VARCHAR(255) | — | Form config name |
form_page | VARCHAR(255) | — | Path the form was submitted from |
tracking_data | LONGTEXT | — | JSON blob — full tracking payload |
contact_data | LONGTEXT | — | JSON blob — full contact payload |
created_at | DATETIME | ✅ | Defaults to CURRENT_TIMESTAMP |
The dedicated columns are copies pulled out of the two JSON blobs at insert time. tracking_data
also records a capture_method key (ajax for supported AJAX forms, beacon-best-effort for
best-effort captures) so you can tell reliable captures from best-effort ones.
Note The JSON blobs are not indexed. If you need to query on a value that lives only inside a
blob, extract it in application code rather than addingLIKEscans overLONGTEXT.
sdft_log_lead is a public route, so it applies layered checks. A submission must clear all of
them, in this order, before the sdft_validate_lead_submission filter even runs:
sdft_log_lead_nonce must verify.Origin or Referer host matches the site host. Ifsdft_hp_field must be empty.base64(json).hmac_sha256 token signed with wp_salt('nonce'). Thehash_equals, the embedded timestamp must be within a 5-minute (300s)INSERT IGNORE lock plus a consumed-token transient ensure each token isvisitor_id + form_page (+ form_name) withinThe sdft_lead_token endpoint exists specifically for full-page caches: an inline token baked into
cached HTML would already be expired by the time a visitor submits. The tracker instead fetches a
fresh nonce + token from sdft_lead_token (which sends nocache_headers()) immediately before
posting, so capture works without any per-host cache rules.
sdftConfigThe tracker script reads a single localized global, window.sdftConfig. It is only printed when at
least one tracking field is enabled. Keys include: trackingFields, formConfigs, storageType,debugEnabled, visitedPagesLimit, consentMode (plus consentCookieName / consentCookieValue
in manual mode), plan, and — when lead tracking is active — leadTracking, ajaxUrl,leadNonce, and leadToken.
Note Treat
sdftConfigas read-only output. It reflects server-side, plan-filtered settings;
overwriting it in the browser will not unlock gated features (the server re-checks the plan on
every request).
?sdft_scan=1Appending ?sdft_scan=1 to any front-end URL loads the scanner script instead of the tracker.
It walks the page’s forms and reports usable CSS selectors, which powers the “Scan page” button in
the admin. Scan mode is admin-only — it does nothing unless the current user has manage_options
— so it is safe to leave the pattern in place; visitors can’t trigger it.
You’ve spent the budget. You’ve run the campaigns. You deserve to know what actually worked. SmartDingo gives you complete, accurate, first-party attribution for every WordPress form submission. Start today on the forms you already use.
SmartDingo is a WordPress lead tracking plugin built for online marketers and WordPress developers who need accurate, cookieless attribution. It captures UTM parameters, traffic sources, landing pages, and full visitor journeys for every form submission, working seamlessly with Fluent Forms, Gravity Forms, Ninja Forms, WPForms, and other major WordPress form plugins. Whether you’re tracking leads from Google Ads, Meta, LinkedIn, or organic search, SmartDingo connects every lead to the marketing campaign that generated it.