SpamBlock Integration Guide for HTML and JS Apps

Drop the SpamBlock script into static sites or custom JavaScript apps, listen for events, and control submissions with hooks and copy-ready snippets.

Overview

Add the SpamBlock pixel to any static site or custom JavaScript application. The script protects native HTML forms by default and exposes hooks if you need more control.

Prerequisites

  • Ability to edit the HTML template or bundle a JavaScript asset
  • Forms that render as native <form> elements in the DOM
  • Optional: build tooling if you prefer to load the script from a module bundler

Setup

Quick start

<script src="https://pixel.spamblock.io/latest.js" defer></script>

<form data-block-spam action="https://api.spamblock.io/f/{form_id}" method="post">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

Add data-block-spam to any form you want protected. If you omit the attribute, all forms on the page are protected automatically.

Configuration

All configuration options are set via data-* attributes on the script tag. See the complete configuration reference for all available options and their defaults.

Domain Verification

When you add a domain in your SpamBlock account, you'll receive a verification token. Include this token in your script tag using the data-verify-token attribute:

<script src="https://pixel.spamblock.io/latest.js" data-verify-token="your-verification-token" defer></script>

Automatic Verification: Domains are automatically verified on the first form submission that includes the matching verification token. You don't need to manually verify domains - just add the script with your token and submit a form. The domain will be verified automatically, and you'll be able to access analytics and webhooks for that domain.

Note: Verification tokens are only used for domain ownership verification. They don't affect spam detection or form protection - your forms are protected regardless of verification status.

Advanced usage

Manual protection

window.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('contact-form');
  if (form) {
    form.setAttribute('data-block-spam', 'true');
  }
});

React example

See the React integration guide for detailed React and Next.js examples.

Form Submission Methods

SpamBlock supports two submission patterns: native form submission (default) and JavaScript-based submission (for SPAs and custom handling).

Native Form Submission (Default)

By default, SpamBlock uses native form submission via form.requestSubmit(). This preserves browser validation, form actions, and submitter buttons. The form will navigate to the URL specified in the action attribute.

<form data-block-spam action="https://api.spamblock.io/f/{form_id}" method="post">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

When to use:

  • Traditional server-rendered forms
  • Forms that submit to server endpoints
  • When you want browser-native validation and navigation
  • Simple, zero-configuration setups

How it works:

  1. SpamBlock intercepts the submit event
  2. Assesses the submission with the API and injects markers
  3. Calls form.requestSubmit() to trigger native submission
  4. Browser navigates to the form's action URL with markers included
  5. Backend filters spam based on marker data

JavaScript-Based Submission

For single-page applications (SPAs) or when you need custom submission handling, you need to prevent the pixel's default native submission and handle it manually. Here's how:

Important: The pixel emits spamblock:allowed with the assessment, then calls form.requestSubmit(). To prevent native submission and handle it yourself, you have two options:

Option 1: Remove the action attribute (Recommended)

<form data-block-spam id="contact-form">
  <!-- No action attribute - form won't navigate -->
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
  <div id="status-message"></div>
</form>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('contact-form');
  
  // Intercept allowed event to handle submission manually
  form.addEventListener('spamblock:allowed', async (event) => {
    // Get form data (markers are automatically included!)
    const formData = new FormData(form);
    
    // Submit via fetch (or your preferred method)
    try {
      const response = await fetch('/api/contact', {
        method: 'POST',
        body: formData
      });
      
      if (response.ok) {
        document.getElementById('status-message').textContent = '✅ Message sent!';
        form.reset();
      } else {
        throw new Error('Submission failed');
      }
    } catch (error) {
      document.getElementById('status-message').textContent = '❌ Error sending message';
    }
  });
  
});
</script>

Option 2: Intercept the original submit event

<form data-block-spam id="contact-form" action="https://api.spamblock.io/f/{form_id}" method="post">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
  <div id="status-message"></div>
</form>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('contact-form');
  let shouldSubmitNatively = false;
  
  // Intercept submit BEFORE SpamBlock to prevent native submission
  form.addEventListener('submit', (event) => {
    // Only allow native submission if we explicitly set the flag
    if (!shouldSubmitNatively) {
      event.preventDefault();
    }
  }, true); // Use capture phase - runs before SpamBlock handler
  
  // Handle SpamBlock allowed event
  form.addEventListener('spamblock:allowed', async (event) => {
    // Prevent the pixel's requestSubmit() from actually submitting
    // by ensuring preventDefault was already called
    // Markers are automatically included in FormData!
    const formData = new FormData(form);
    
    try {
      const response = await fetch('/api/contact', {
        method: 'POST',
        body: formData
      });
      
      if (response.ok) {
        document.getElementById('status-message').textContent = '✅ Message sent!';
        form.reset();
      }
    } catch (error) {
      document.getElementById('status-message').textContent = '❌ Error sending message';
    }
  });
  
});
</script>

When to use:

  • Single-page applications (React, Vue, Angular)
  • Forms that need to stay on the same page
  • Custom submission logic (API calls, analytics, etc.)
  • When you need full control over the submission flow

Key differences:

  • Native: Form navigates to action URL, browser handles everything
  • JavaScript: Form stays on page, you handle submission and UI

Listening for results

The pixel emits spamblock:allowed after every assessment (all submissions proceed). Use it to run custom logic before the form submits:

// Assessment complete - form will submit with markers
form.addEventListener('spamblock:allowed', (event) => {
  const response = event.detail.response;
  console.log('Assessment:', response);
  // response contains: allow, score (0-100+), reasons, latencyMs
});

Event details:

  • event.detail.response - The SpamBlock API response
  • event.detail.latencyMs - Time taken for the check (milliseconds)
  • event.detail.site - The site domain that was checked
  • event.detail.markers - SpamBlock marker data (for JavaScript submissions)

Preventing native submission: The pixel calls form.requestSubmit() after emitting spamblock:allowed. To handle submission yourself instead:

  • Option 1 (Recommended): Remove the action attribute from your form. Without an action, requestSubmit() won't navigate anywhere.
  • Option 2: Intercept the original submit event in the capture phase (before SpamBlock) and call preventDefault() to stop all submission attempts.

SpamBlock Markers (Automatic)

SpamBlock automatically includes hidden marker fields (spamblock_v, spamblock_allow, spamblock_score, spamblock_reasons, spamblock_ts) in all form submissions. You don't need to do anything - markers are included automatically for both native and JavaScript-based submissions.

How it works:

  • Native form submissions: Markers are injected as hidden <input> elements in the form
  • JavaScript-based submissions: When you create new FormData(form), SpamBlock automatically includes the markers

Example (no marker handling needed):

form.addEventListener('spamblock:allowed', async (event) => {
  // Just create FormData - markers are automatically included!
  const formData = new FormData(form);
  
  await fetch('/api/contact', {
    method: 'POST',
    body: formData
  });
});

Marker fields (automatically included):

  • spamblock_v - Schema version (currently "1")
  • spamblock_allow - "true" or "false" (spam assessment; backend uses this to filter)
  • spamblock_score - Risk score (0-100+)
  • spamblock_reasons - Comma-separated list of detected spam indicators
  • spamblock_ts - ISO 8601 timestamp of when the check was performed

Why markers? Your backend filters spam using the markers (e.g. treat score ≥ 60 as spam). Markers also let you log score/reasons for analytics and detect if someone bypasses the pixel.

Testing

  • Enable debug logging by setting data-debug="true" on the script tag.
  • Submit a disposable address like qa@mailinator.com to trigger the disposable domain signal.
  • Try spam-heavy text ("casino winner viagra") to trigger profanity detection.
  • Use browser devtools to ensure the honeypot field spamblock_hp is present and left blank.

For more testing tips and configuration options, see the documentation.

Troubleshooting

Symptom Fix
Form never submits Check for competing JavaScript that also prevents submission.
Backend filtering too strict Adjust data-max-score or your backend threshold. Inspect console logs (with data-debug="true") for the reasons array.
No spam being caught Confirm the pixel loads on the page and the <form> has data-block-spam.

For more troubleshooting tips and configuration options, see the documentation.