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://api.spamblock.io/sdk/pixel/v1.js" defer></script>
<form data-block-spam action="/api/contact" 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://api.spamblock.io/sdk/pixel/v1.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="/api/contact" 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:
- SpamBlock intercepts the submit event
- Checks the submission with the API
- If allowed: calls
form.requestSubmit()to trigger native submission - Browser navigates to the form's
actionURL
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 calls form.requestSubmit() immediately after emitting the spamblock:allowed event. To prevent native submission, 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';
}
});
// Handle blocked submissions
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
const reasons = response && response.reasons ? response.reasons.join(', ') : 'Spam detected';
document.getElementById('status-message').textContent =
'❌ Submission blocked: ' + reasons;
});
});
</script>
Option 2: Intercept the original submit event
<form data-block-spam id="contact-form" action="/api/contact" 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';
}
});
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
const reasons = response && response.reasons ? response.reasons.join(', ') : 'Spam detected';
document.getElementById('status-message').textContent =
'❌ Submission blocked: ' + reasons;
});
});
</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
actionURL, browser handles everything - JavaScript: Form stays on page, you handle submission and UI
Listening for results
The pixel dispatches custom DOM events you can hook into:
// Submission was allowed - form will submit natively by default
form.addEventListener('spamblock:allowed', (event) => {
const response = event.detail.response;
console.log('Submission allowed', response);
// response contains: allow: true, score: 0-60, reasons: [], latencyMs: 123
});
// Submission was blocked - form will NOT submit
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
console.warn('Spam detected', response);
// response contains: allow: false, score: 61+, reasons: ['disposable_domain'], latencyMs: 123
});
Event details:
event.detail.response- The SpamBlock API responseevent.detail.latencyMs- Time taken for the check (milliseconds)event.detail.site- The site domain that was checkedevent.detail.markers- SpamBlock marker data (for JavaScript submissions)
Preventing default submission:
The pixel automatically calls form.requestSubmit() after emitting spamblock:allowed. To prevent this:
- Option 1 (Recommended): Remove the
actionattribute from your form. Without an action,requestSubmit()won't navigate anywhere. - Option 2: Intercept the original
submitevent in the capture phase (before SpamBlock) and callpreventDefault()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" (whether submission was allowed)spamblock_score- Risk score (0-100+)spamblock_reasons- Comma-separated list of detected spam indicatorsspamblock_ts- ISO 8601 timestamp of when the check was performed
Why markers? Your backend can verify that submissions passed SpamBlock checks and log the score/reasons for analytics. This helps detect if someone bypasses the client-side pixel.
Testing
- Enable debug logging by setting
data-debug="true"on the script tag. - Submit a disposable address like
qa@mailinator.comto 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_hpis 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. |
| Legitimate submissions blocked | Adjust data-max-score or inspect console logs (with data-debug="true") for the reasons array. See configuration options. |
| 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.