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; }