How do I implement Webmentions on my site?
Step-by-step guide to adding Webmention support to your website, from beginner to advanced.
Implementing Webmentions on your site can range from simple to sophisticated, depending on your needs and technical comfort level. This guide covers options for everyone.
Quick Start: Using Webmentions.com
The fastest way to get started is using our hosted service:
Step 1: Sign Up
- Visit webmentions.com
- Create an account and add your domain
- Get your Webmention endpoint
Step 2: Add the Endpoints
Add this to your HTML <head>:
<!-- Webmention endpoint (required) -->
<link rel="webmention" href="https://receive.webmentions.com/yourdomain.com" />
<!-- Pingback endpoint (optional - for legacy WordPress sites) -->
<link rel="pingback" href="https://receive.webmentions.com/xmlrpc/yourdomain.com" />
Replace yourdomain.com with your actual domain.
The pingback endpoint is optional but recommended - it captures mentions from older blogs that haven't adopted Webmentions yet. Both types appear unified in your dashboard.
Step 3: Display Mentions
Fetch and display mentions using our API:
fetch('https://api.webmentions.com/mentions?target=https://yourdomain.com/post')
.then(res => res.json())
.then(data => {
data.mentions.forEach(mention => {
// Display each mention
console.log(mention);
});
});
That's it! Your site can now receive Webmentions.
Intermediate: Static Site Integration
For static sites (Hugo, Jekyll, Eleventy, etc.), you can integrate Webmentions at build time.
Jekyll Example
-
Install the plugin:
gem install jekyll-webmention_io -
Configure in
_config.yml:webmentions: username: yourdomain.com -
Display in templates:
{% webmentions page.url %}
Eleventy Example
-
Install the plugin:
npm install @chrisburnell/eleventy-plugin-webmentions -
Configure in
.eleventy.js:const webmentions = require('@chrisburnell/eleventy-plugin-webmentions'); module.exports = function(config) { config.addPlugin(webmentions, { domain: 'https://yourdomain.com', token: process.env.WEBMENTION_IO_TOKEN }); }; -
Use in templates:
{% webmentions page.url %}
Advanced: Custom Implementation
For full control, implement your own Webmention endpoint.
Server Setup (Node.js)
1. Create the Endpoint:
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.post('/webmention', express.urlencoded({ extended: true }), async (req, res) => {
const { source, target } = req.body;
// Validate parameters
if (!isValidUrl(source) || !isValidUrl(target)) {
return res.status(400).send('Invalid URLs');
}
// Check if target belongs to this site
if (!target.startsWith('https://yourdomain.com')) {
return res.status(400).send('Target not on this domain');
}
// Queue for asynchronous verification
await queue.add('verify-webmention', { source, target });
// Return 202 Accepted
res.status(202).send('Webmention accepted for verification');
});
2. Implement Verification:
async function verifyWebmention(source, target) {
try {
// Fetch the source page
const response = await fetch(source, {
headers: { 'User-Agent': 'YourWebmentionBot/1.0' }
});
if (!response.ok) {
throw new Error('Failed to fetch source');
}
const html = await response.text();
// Check if target URL exists in source
if (!html.includes(target)) {
throw new Error('Target not found in source');
}
// Parse microformats
const mf2 = await parseMicroformats(html, source);
// Store the verified mention
await saveMention({
source,
target,
verified: true,
data: mf2,
receivedAt: new Date()
});
return true;
} catch (error) {
console.error('Verification failed:', error);
return false;
}
}
3. Parse Microformats:
const microformats = require('microformats-parser');
async function parseMicroformats(html, url) {
const mf2 = microformats.get({ html, baseUrl: url });
// Extract h-entry data
const entry = mf2.items.find(item => item.type.includes('h-entry'));
if (!entry) {
return null;
}
return {
type: detectMentionType(entry),
author: extractAuthor(entry),
content: extractContent(entry),
published: entry.properties.published?.[0],
url: entry.properties.url?.[0] || url
};
}
function detectMentionType(entry) {
const props = entry.properties;
if (props['in-reply-to']) return 'reply';
if (props['like-of']) return 'like';
if (props['repost-of']) return 'repost';
if (props['bookmark-of']) return 'bookmark';
return 'mention';
}
Database Schema
Store mentions in your database:
CREATE TABLE webmentions (
id SERIAL PRIMARY KEY,
source VARCHAR(500) NOT NULL,
target VARCHAR(500) NOT NULL,
verified BOOLEAN DEFAULT false,
mention_type VARCHAR(50),
author_name VARCHAR(200),
author_url VARCHAR(500),
author_photo VARCHAR(500),
content TEXT,
published_at TIMESTAMP,
received_at TIMESTAMP DEFAULT NOW(),
moderation_status VARCHAR(50) DEFAULT 'pending'
);
CREATE INDEX idx_target ON webmentions(target);
CREATE INDEX idx_verified ON webmentions(verified);
Sending Webmentions
To send Webmentions when you publish content:
Automatic Sending
const cheerio = require('cheerio');
async function sendWebmentionsForPost(postUrl, postHtml) {
const $ = cheerio.load(postHtml);
const links = $('a[href]').map((i, el) => $(el).attr('href')).get();
// Filter external links
const externalLinks = links.filter(link =>
link.startsWith('http') && !link.includes('yourdomain.com')
);
// Send Webmention to each
for (const targetUrl of externalLinks) {
try {
await sendWebmention(postUrl, targetUrl);
} catch (error) {
console.error(`Failed to send Webmention to ${targetUrl}:`, error);
}
}
}
async function sendWebmention(source, target) {
// Discover endpoint
const endpoint = await discoverWebmentionEndpoint(target);
if (!endpoint) {
console.log(`No Webmention endpoint found for ${target}`);
return;
}
// Send the mention
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ source, target })
});
return response.status === 202 || response.status === 200;
}
Endpoint Discovery
async function discoverWebmentionEndpoint(url) {
const response = await fetch(url);
// Check Link header
const linkHeader = response.headers.get('link');
if (linkHeader) {
const match = linkHeader.match(/<([^>]+)>;\s*rel="webmention"/);
if (match) {
return new URL(match[1], url).href;
}
}
// Check HTML
const html = await response.text();
const $ = cheerio.load(html);
const linkEl = $('link[rel="webmention"]').first();
if (linkEl.length) {
return new URL(linkEl.attr('href'), url).href;
}
return null;
}
Security Best Practices
Implement these security measures:
-
Rate Limiting:
const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); app.use('/webmention', limiter); -
Validation:
- Verify URLs are properly formed
- Check target belongs to your domain
- Ensure source is publicly accessible
- Validate source contains target link
-
Spam Prevention:
- Implement moderation queue
- Use blocklists for known spam domains
- Require manual approval for first-time domains
- Check for suspicious patterns
-
Content Sanitization:
- Strip dangerous HTML/JavaScript
- Limit content length
- Validate image URLs
- Escape output when displaying
Testing Your Implementation
Use these resources:
- Webmentions.com: Our hosted service for receiving, sending, and managing mentions - the easiest way to test your implementation
- webmention.rocks: Comprehensive test suite for validating your implementation against the W3C spec
- Telegraph: Alternative manual Webmention sender
Next Steps
Once you have basic Webmentions working:
- Add microformats to your content
- Implement richer display of mentions
- Set up automatic sending
- Add moderation workflows
- Integrate with social media via Bridgy
The IndieWeb wiki has extensive documentation and examples to help you along the way.
Related FAQs
How do I add Webmentions to a static site?
Implementing Webmentions on static sites built with Jekyll, Hugo, Eleventy, Next.js, and other generators.
How do I add Webmentions to WordPress?
Complete guide to implementing Webmentions on WordPress sites using plugins and custom solutions.
How do I handle spam Webmentions?
Strategies and techniques for preventing, detecting, and moderating spam Webmentions on your website.