AT Valid Widget โ€” Demo & Integrations

Real-time email validation for any form. Try it below, then copy the snippet for your platform.

โ†“ Jump to integration examples

Security verification required before using the demo

Detailed Validation

Full result with score, grade, and flags

Email
Score
Grade
Disposable
Free Provider

Auto-Validate (Widget Mode)

widget.js with allow rules, HTML5 validation, preventSubmit

// Widget events will appear here...

Programmatic API

ATValid.validate(email, callback) โ€” validate without DOM binding

// Programmatic result will appear here...

Integration examples

One script tag, one ATValid.init(). Replace pk_live_โ€ฆ with the public key from app.atvalid.com/apis (restrict it to your domains).

Plain HTML โ€” quick start

Any site: paste before </body>. Every input[type="email"] is validated on blur.

<!-- Add before </body> -->
<script src="https://www.atvalid.com/widget/v2/widget.js"></script>
<script>
  ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    preventSubmit: true            // block the submit while the email is invalid
  });
</script>
The widget uses a MutationObserver, so forms injected later (AJAX, SPAs, page builders) are detected automatically. Nothing is sent to the address โ€” verification happens server-side.

All options, allow rules and messages

Defaults shown. messages.sub (v2.1) gives a specific reason instead of a generic "invalid".

ATValid.init({
  publicKey: 'pk_live_your_public_key_here',
  selector: 'input[type="email"]',   // which inputs to bind
  validateOn: 'blur',                // 'blur' | 'input'
  verifyAfterDelay: 1000,            // ms after typing stops (validateOn: 'input')
  timeout: 10,                       // seconds
  showFeedback: true,                // message under the input
  preventSubmit: false,              // block submit when blocked/invalid
  formValidation: false,             // HTML5 setCustomValidity()
  blockOnRateLimit: false,
  statusAppendTo: null,              // CSS selector or element for the feedback
  ignoredForms: [],                  // form names/ids to skip
  ignoredInputs: [],                 // input names/ids to skip

  allow: {
    states: ['deliverable', 'risky', 'unknown'],  // 'undeliverable' is never accepted by default
    free: true,                      // Gmail, Outlook, Yahooโ€ฆ
    role: true,                      // info@, sales@, admin@โ€ฆ
    disposable: false                // temp-mail domains
  },

  messages: {
    checking: 'Verifying email...',
    valid: 'Valid email address',
    invalid: 'This email appears to be invalid',
    disposable: 'Disposable email addresses are not allowed',
    role: 'Role-based email addresses are not allowed',
    free: 'Free email addresses are not allowed',
    suggestion: 'Did you mean {suggestion}?',
    networkError: 'Could not verify email. Please check your connection.',
    rateLimited: 'Too many requests. Please wait a moment.',
    noCredits: 'Validation service temporarily unavailable',
    error: 'Could not verify email at this time',
    // v2.1 โ€” specific reasons (partial overrides are fine)
    sub: {
      not_found: 'This mailbox does not exist',
      mailbox_full: 'This mailbox is full and cannot receive email',
      null_mx: 'This domain does not accept email',
      no_mx: 'This domain has no mail server',
      blacklist: 'This email domain is blacklisted',
      parked: 'This domain is parked and does not receive email',
      no_reply: 'No-reply addresses cannot be used',
      greylist: 'The mail server asked us to try again later',
      toxic: 'This email address cannot be accepted',
      catch_all: 'We could not confirm this mailbox exists',
      smtp_unknown: 'The mail server did not respond',
      suspicious: 'This email address looks suspicious'
    }
  },

  onValidate: function (email, result) { console.log(email, result); },
  onError: function (email, error) { console.error(email, error.code, error.message); }
});

WordPress โ€” Contact Form 7, WPForms, Gravity Forms, Elementor, Formidable, Ninja Forms

Add to footer.php before </body>, or with the "Insert Headers and Footers" / "WPCode" plugin (site-wide footer).

<script src="https://www.atvalid.com/widget/v2/widget.js"></script>
<script>
  ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    selector: [
      '.wpcf7-email',                          // Contact Form 7
      '.wpforms-field-email input',            // WPForms
      '.gfield input[type="email"]',           // Gravity Forms
      '.elementor-field-type-email input',     // Elementor Forms
      '.frm_form_field input[type="email"]',   // Formidable
      '.nf-form-cont input[type="email"]',     // Ninja Forms
      'input[type="email"]'                    // anything else
    ].join(', '),
    validateOn: 'blur',
    preventSubmit: true,
    allow: { disposable: false, role: false }
  });
</script>
Contact Form 7 re-renders after an AJAX submit โ€” the widget re-binds automatically. If a plugin caches/minifies JS, exclude widget.js from combining.

RD Station โ€” landing pages & pop-ups

Landing page โ†’ Settings โ†’ "Custom code (HTML/JS)" โ†’ before </body>. RD forms load dynamically; the widget waits for them.

<script src="https://www.atvalid.com/widget/v2/widget.js"></script>
<script>
  ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    selector: 'input[type="email"], input[name="email"]',
    preventSubmit: true,
    formValidation: true,     // RD's own validation shows our message
    allow: { disposable: false }
  });
</script>
For RD Station forms embedded on your own site (script embed), add the same snippet to the page that hosts the embed.

HubSpot โ€” forms, landing pages, pop-ups

Settings โ†’ Website โ†’ Pages โ†’ Advanced โ†’ "Site footer HTML" (or per page). HubSpot forms are rendered dynamically โ€” detected automatically.

<script src="https://www.atvalid.com/widget/v2/widget.js"></script>
<script>
  ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    selector: 'input[type="email"], .hs-input[name="email"]',
    preventSubmit: true,
    allow: { disposable: false }
  });

  // Optional: re-scan when HubSpot signals a form is ready
  window.addEventListener('message', function (e) {
    if (e.data && e.data.type === 'hsFormCallback' && e.data.eventName === 'onFormReady') {
      ATValid.refresh();
    }
  });
</script>

Mailchimp โ€” embedded signup form

Paste after the Mailchimp embed code on your site (Mailchimp-hosted landing pages do not accept custom JS).

<!-- your Mailchimp embed form is above -->
<script src="https://www.atvalid.com/widget/v2/widget.js"></script>
<script>
  ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    selector: '#mc-embedded-subscribe-form input[type="email"], #mce-EMAIL',
    validateOn: 'input',        // validate while typing (debounced 1s)
    verifyAfterDelay: 1000,
    preventSubmit: true,
    allow: { disposable: false, role: false }
  });
</script>

Google Tag Manager โ€” Custom HTML tag

Tag type "Custom HTML", trigger "All Pages" (or only pages with forms). Works on any CMS without touching the theme.

<script>
  (function () {
    var s = document.createElement('script');
    s.src = 'https://www.atvalid.com/widget/v2/widget.js';
    s.async = true;
    s.onload = function () {
      ATValid.init({
        publicKey: 'pk_live_your_public_key_here',
        preventSubmit: true,
        allow: { disposable: false },
        onValidate: function (email, result) {
          // push to the dataLayer for GA4 / conversion rules
          window.dataLayer = window.dataLayer || [];
          window.dataLayer.push({
            event: 'atvalid_email_verified',
            atv_accepted: result.accepted,
            atv_state: result.state,
            atv_sub_status: result.subStatus,
            atv_score: result.score
          });
        }
      });
    };
    document.head.appendChild(s);
  })();
</script>
The snippet does not need "Support document.write". The onValidate callback pushes an event to the dataLayer for GA4 / conversion rules.

Webflow, Wix, Shopify, Squarespace โ€” site-wide custom code

Webflow: Project settings โ†’ Custom code โ†’ Footer. Wix: Settings โ†’ Custom code โ†’ Body-end. Shopify: theme.liquid before </body>. Squarespace: Settings โ†’ Advanced โ†’ Code injection โ†’ Footer.

<script src="https://www.atvalid.com/widget/v2/widget.js"></script>
<script>
  ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    // covers Webflow (.w-input), Wix, Shopify (customer/contact forms) and Squarespace
    selector: 'input[type="email"], input.w-input[type="email"], input[name="customer[email]"], input[name="contact[email]"]',
    preventSubmit: true,
    allow: { disposable: false }
  });
</script>
Shopify checkout does not allow custom JS: use the widget on storefront forms (newsletter, contact, account) and the REST API server-side for checkout.

React / Next.js

Load once, bind to your input, destroy on unmount. In Next.js mark the component 'use client'.

import { useEffect } from 'react';

export function ContactForm() {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://www.atvalid.com/widget/v2/widget.js';
    script.async = true;
    script.onload = () => {
      window.ATValid.init({
        publicKey: 'pk_live_your_public_key_here',
        selector: '#email-input',
        preventSubmit: true,
        formValidation: true,
        allow: { disposable: false },
        onValidate: (email, result) => console.log(email, result.accepted, result.subStatus)
      });
    };
    document.body.appendChild(script);
    return () => {
      window.ATValid && window.ATValid.destroy();
      document.body.removeChild(script);
    };
  }, []);

  return (
    <form>
      <input id="email-input" type="email" placeholder="Email" />
      <button type="submit">Submit</button>
    </form>
  );
}
After client-side navigation that renders new forms, call ATValid.refresh() (or rely on the MutationObserver, on by default).

Vue 3 / Nuxt

Same idea with onMounted / onBeforeUnmount. In Nuxt keep it inside <ClientOnly> or a client component.

<script setup>
import { onMounted, onBeforeUnmount } from 'vue';

let script;
onMounted(() => {
  script = document.createElement('script');
  script.src = 'https://www.atvalid.com/widget/v2/widget.js';
  script.async = true;
  script.onload = () => window.ATValid.init({
    publicKey: 'pk_live_your_public_key_here',
    selector: '#email',
    preventSubmit: true,
    allow: { disposable: false }
  });
  document.body.appendChild(script);
});
onBeforeUnmount(() => {
  window.ATValid && window.ATValid.destroy();
  script && script.remove();
});
</script>

<template>
  <form><input id="email" type="email" /><button>Submit</button></form>
</template>

Callbacks, DOM events and the result object (v2.1)

Use onValidate or listen to atv:verified / atv:error on document.

document.addEventListener('atv:verified', function (e) {
  var r = e.detail.result;
  // r = {
  //   valid: true|false,            // API-level verdict
  //   accepted: true|false,         // passed your allow rules โ†’ decides the submit
  //   state: 'deliverable'|'risky'|'unknown'|'undeliverable',
  //   subStatus: 'ok'|'not_found'|'mailbox_full'|'disposable'|'null_mx'|'no_mx'|'typo'|
  //              'blacklist'|'parked'|'no_reply'|'greylist'|'catch_all'|'suspicious'|
  //              'role_based'|'toxic'|'smtp_unknown'|'risky'|'invalid',   (v2.1)
  //   toxicity: 0..5,               // reputation risk (โ‰ฅ3 is never accepted)  (v2.1)
  //   catchAllConfidence: 0..10|null, // evidence the mailbox exists on a catch-all domain (v2.1)
  //   score: 0..100, grade: 'A'..'F',
  //   disposable, free, role: booleans,
  //   suggestion: '[email protected]' | null,   // typo fix (clickable in the feedback)
  //   email: '[email protected]',
  //   raw: { ...full API response }
  // }
  if (!e.detail.accepted) console.log('blocked:', r.subStatus);
});

document.addEventListener('atv:error', function (e) {
  // e.detail.error: NETWORK_ERROR | TIMEOUT | RATE_LIMITED | NO_CREDITS |
  //                 DOMAIN_BLOCKED | AUTH_ERROR | API_ERROR | PARSE_ERROR | NO_KEY
  console.warn(e.detail.email, e.detail.error);
});
Attribute on the inputValuesMeaning
data-atv-valid"true" | "false"Accepted by your rules (what preventSubmit checks)
data-atv-statedeliverable | risky | unknown | undeliverableDerived state
data-atv-bound"1"Widget is attached to this input

Programmatic API โ€” no DOM binding

Validate on demand (e.g. before an XHR submit), re-scan, clear cache, tear down.

// Validate a single address (publicKey optional if init() already ran)
ATValid.validate('[email protected]', 'pk_live_your_key', function (result) {
  console.log(result.accepted, result.state, result.subStatus, result.score, result.toxicity);
});

ATValid.refresh();     // re-scan the DOM for new email inputs (SPA navigation)
ATValid.clearCache();  // forget the 5-minute in-page cache
ATValid.destroy();     // remove listeners, observer, styles and feedback
Rate limits for public keys: 60 requests/min per key and 30/min per visitor IP. Results are cached in the page for 5 minutes, so re-typing the same address costs nothing.

Styling

The widget injects minimal CSS; override any class in your stylesheet.

.atv-input-valid    { border-color: #22c55e !important; box-shadow: 0 0 0 3px rgba(34,197,94,.1) !important; }
.atv-input-invalid  { border-color: #ef4444 !important; box-shadow: 0 0 0 3px rgba(239,68,68,.1) !important; }
.atv-input-checking { border-color: #9ca3af !important; }

.atv-feedback   { font-size: .8125rem; margin-top: .25rem; font-family: inherit; }
.atv-valid      { color: #22c55e; }
.atv-invalid    { color: #ef4444; }
.atv-checking   { color: #9ca3af; }
.atv-suggestion { color: #d97706; cursor: pointer; text-decoration: underline; }
.atv-suggestion:hover { color: #b45309; }