Webmentions¶
Webmention is a W3C recommendation that enables cross-site conversations. When you link to someone else’s content, you can send them a webmention to notify them. Similarly, when others link to your content, they can send you webmentions that you can display as comments, likes, or reposts.
Features¶
Receiving webmentions: Accept and validate incoming webmentions
Sending webmentions: Automatically discover and notify linked sites
Microformats2 parsing: Extract semantic content from webmentions
Spam protection: Pluggable spam checker interface
Display templates: Show webmentions as comments, likes, and reposts
Management commands: Send webmentions from the command line
Quick Start¶
Add the webmention endpoint to your base template:
{% load webmention_tags %}
<head>
{% webmention_endpoint_link %}
</head>
Display webmentions on your pages:
{% load webmention_tags %}
{% show_webmentions request.build_absolute_uri %}
Send webmentions when you publish content:
python manage.py send_webmentions https://mysite.com/new-post/
Configuration¶
Add these settings to your Django settings:
# Required: URL resolver to map URLs to content objects
INDIEWEB_URL_RESOLVER = 'myproject.webmention_config.MyURLResolver'
# Required: Spam checker (use default or implement your own)
INDIEWEB_SPAM_CHECKER = 'indieweb.interfaces.NoOpSpamChecker'
# Optional: Comment adapter to convert webmentions to comments
INDIEWEB_COMMENT_ADAPTER = 'myproject.webmention_config.MyCommentAdapter'
Implementing URL Resolution¶
Create a URL resolver to map target URLs to your content objects:
# myproject/webmention_config.py
from indieweb.interfaces import URLResolver
from myapp.models import BlogPost
from urllib.parse import urlparse
class MyURLResolver(URLResolver):
def resolve(self, target_url: str) -> Any | None:
"""Resolve a URL to a content object."""
parsed = urlparse(target_url)
path = parsed.path
# Example: match /blog/post-slug/
if path.startswith('/blog/'):
slug = path.strip('/').split('/')[-1]
try:
return BlogPost.objects.get(slug=slug)
except BlogPost.DoesNotExist:
pass
return None
def get_absolute_url(self, content_object: Any) -> str:
"""Get the absolute URL for a content object."""
if hasattr(content_object, 'get_absolute_url'):
return content_object.get_absolute_url()
return ''
Implementing Spam Protection¶
Create a custom spam checker:
from indieweb.interfaces import SpamChecker
from indieweb.models import Webmention
class MySpamChecker(SpamChecker):
def check(self, webmention: Webmention) -> dict[str, Any]:
"""Check if a webmention is spam."""
# Implement your spam detection logic
spam_keywords = ['casino', 'viagra', 'lottery']
content_lower = webmention.content.lower()
is_spam = any(kw in content_lower for kw in spam_keywords)
return {
'is_spam': is_spam,
'confidence': 0.9 if is_spam else 0.1,
'details': 'Keyword-based detection'
}
Template Usage¶
Basic Usage¶
{% load webmention_tags %}
{# Add endpoint discovery to your base template #}
{% webmention_endpoint_link %}
{# Show all webmentions for current page #}
{% show_webmentions request.build_absolute_uri %}
{# Show only replies #}
{% show_webmentions request.build_absolute_uri mention_type="reply" %}
{# Get webmention count #}
{% webmention_count request.build_absolute_uri as count %}
<p>This post has {{ count }} responses.</p>
Custom Templates¶
You can override the default templates by creating your own:
indieweb/webmentions.html- Main containerindieweb/webmention_types/like.html- Like templateindieweb/webmention_types/reply.html- Reply templateindieweb/webmention_types/repost.html- Repost templateindieweb/webmention_types/mention.html- Generic mention template
Management Commands¶
send_webmentions¶
Send webmentions for all links in a post:
# Send webmentions for a URL
python manage.py send_webmentions https://mysite.com/new-post/
# Provide content directly
python manage.py send_webmentions https://mysite.com/new-post/ \
--content '<p>Check out <a href="https://example.com">this site</a>!</p>'
# Dry run to see what would be sent
python manage.py send_webmentions https://mysite.com/new-post/ --dry-run
Signals¶
The webmention_received signal is sent when a webmention is processed:
from django.dispatch import receiver
from indieweb.signals import webmention_received
@receiver(webmention_received)
def handle_webmention(sender, webmention, created, **kwargs):
if created and webmention.status == 'verified':
# Send notification, update cache, etc.
print(f"New webmention from {webmention.source_url}")
Models¶
The Webmention model stores all webmention data:
from indieweb.models import Webmention
# Get all verified webmentions for a URL
webmentions = Webmention.objects.filter(
target_url='https://mysite.com/post/',
status='verified'
)
# Get webmentions by type
likes = webmentions.filter(mention_type='like')
replies = webmentions.filter(mention_type='reply')
Fields:
source_url- The URL that links to your contenttarget_url- Your URL that was linked tostatus- pending, verified, failed, or spammention_type- mention, like, reply, or repostauthor_name,author_url,author_photo- Author infocontent,content_html- The mention contentpublished- When the mention was publishedcreated,modified- Timestampsverified_at- When the mention was verifiedspam_check_result- JSON field with spam check details
Testing Webmentions¶
You can test your webmention implementation using webmention.rocks:
Follow the test suite to verify your implementation
Use the discovery tests to check endpoint detection
Use the receiving tests to verify your endpoint
Troubleshooting¶
Common issues and solutions:
- Webmentions not being received
Check that your webmention endpoint is discoverable
Verify the endpoint URL is correct in your Link header/tag
Check Django logs for any errors
- Target URL validation failing
Ensure
django.contrib.sitesis configured correctlyCheck that the Site domain matches your production domain
- Microformats not being parsed
Verify the source page has proper microformats2 markup
Use a validator like https://indiewebify.me/
- Author name shows as URL or profile picture is missing
The source page may use URL references for author data (e.g.,
<data class="p-author" value="https://example.com/author"></data>)This is valid microformats2 markup following the authorship algorithm
Django-indieweb automatically looks for a matching h-card on the same page with the referenced URL
Ensure the source page includes a separate h-card with matching URL, name, and photo properties
The h-card may be nested in structures like h-feeds - the parser searches recursively
Example services using this pattern: feed.city, some Mastodon webmention bridges
If no matching h-card is found, the URL will be displayed as the name (fallback behavior)
Limitation: Django-indieweb does not currently fetch remote author URLs or follow rel=author links (full authorship algorithm)
- Spam checker rejecting valid webmentions
Review your spam checker implementation
Check the
spam_check_resultfield for details
API Reference¶
See API Reference for detailed API documentation of all webmention-related classes and functions.