# Webmentions with batteries included

[//]: # (title: Webmentions with batteries included)
[//]: # (description: A zero-cost library to integrate Webmentions in your website)
[//]: # (id: c4fjqc9t3n)
[//]: # (image: https://s3.fabiomanganiello.com/fabio/img/webmentions-banner.png)
[//]: # (author: Fabio Manganiello <fabio@manganiello.tech>)
[//]: # (published: 2026-02-11T12:19:16+01:00)
[//]: # (updated: 2026-02-11T20:43:27+01:00)

I have been a quite strong advocate of [Webmentions](https://www.w3.org/TR/webmention/) for a long time.

The idea is simple and powerful, and very consistent with the decentralized [POSSE](https://indieweb.org/POSSE) approach to content syndication.

Suppose that Alice finds an interesting article on Bob's website, at `https://bob.com/article`.

She writes a comment about it on her own website, at `https://alice.com/comment`.

If both Alice's and Bob's websites support Webmentions, then their websites will both advertise an e.g. `POST /webmentions` endpoint.

When Alice publishes her comment, her website will send a Webmention to Bob's website, with the source URL (`https://alice.com/comment`) and the target URL (`https://bob.com/article`).

Bob's website will receive the Webmention, verify that the source URL actually mentions the target URL, and then display the comment on the article page.

No 3rd-party commenting system. No intermediate services. No social media login buttons. No ad-hoc comment storage and moderation solutions. Just a simple, decentralized, peer-to-peer mechanism based on existing Web standards.

This is an alternative (and complementary) approach to federation mechanisms like [ActivityPub](https://www.w3.org/TR/activitypub/), which are very powerful but also quite complex to implement, as implementations must deal with concepts such as actors, relays, followers, inboxes, outboxes, and so on.

It is purely peer-to-peer, based on existing Web infrastructure, and with no intermediate actors or services.

Moreover, thanks to [Microformats](https://microformats.org/wiki/microformats2), Webmentions can be used to share any kind of content, not just comments: likes, reactions, RSVPs, media, locations, events, and so on.

However, while the concept is simple, implementing Webmentions support from scratch can be a bit cumbersome, especially if you want to do it right and support [all the semantic elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Microformats).

I have thus proceeded to [implement a simple Python library](https://git.fabiomanganiello.com/webmentions) (but more bindings are [on the backlog](https://git.fabiomanganiello.com/webmentions/projects/9)) that can be easily integrated into any website, and that takes care of all the details of the Webmentions protocol implementation. You only have to worry about writing good semantic HTML, and rendering Webmention objects in your pages.

## Quick start

If you use FastAPI or Flask, serve your website as static files and you're ok to use an SQLAlchemy engine to store Webmentions, you can get started in a few lines of code.

```
pip install "webmentions[db,file,fastapi]"
# For Flask bindings
pip install "webmentions[db,file,flask]"
```

Base implementation:

```python
import os

from webmentions import WebmentionsHandler
from webmentions.storage.adapters.db import init_db_storage
from webmentions.server.adapters.fastapi import bind_webmentions
from webmentions.storage.adapters.file import FileSystemMonitor

# This should match the public URL of your website
base_url = "https://example.com"

# The directory that serves your static articles/posts.
# HTML, Markdown and plain text are supported
static_dir = "/srv/html/articles"

# A function that takes a path to a created/modified/deleted text/* file
# and maps it to a URL on the Web server to be used as the Webmention source
def path_to_url(path: str) -> str:
    # Convert path (absolute) to a path relative to static_dir
    # and drop the extension.
    # For example, /srv/http/articles/2022/01/01/article.md
    # becomes /2022/01/01/article
    path = os.path.relpath(path, static_dir).rsplit(".", 1)[0].lstrip("/")
    # Convert the path to a URL on the Web server
    # For example, /2022/01/01/article
    # becomes https://example.com/articles/2022/01/01/article
    return f"{base_url.rstrip('/')}/articles/{path}"

##### For FastAPI

from fastapi import FastAPI
from webmentions.server.adapters.fastapi import bind_webmentions

app = FastAPI()

##### For Flask

from flask import Flask
from webmentions.server.adapters.flask import bind_webmentions

app = Flask(__name__)

# ...Initialize your Web app as usual...

# Create a Webmention handler

handler = WebmentionsHandler(
    storage=init_db_storage(engine="sqlite:////tmp/webmentions.db"),
    base_url=base_url,
)

# Bind Webmentions to your app
bind_webmentions(app, handler)

# Create and start the filesystem monitor before running your app
with FileSystemMonitor(
    root_dir=static_dir,
    handler=handler,
    file_to_url_mapper=path_to_url,
) as monitor:
    app.run(...)
```

This will:

- Register a `POST /webmentions` endpoint to receive Webmentions
- Advertise the Webmentions endpoint in every `text/*` response provided by the server
- Expose a `GET /webmentions` endpoint to list Webmentions (takes `resource`
  URL and `direction` (`in` or `out`) query parameters)
- Store Webmentions in a database (using SQLAlchemy)
- Monitor `static_dir` for changes to HTML or text files, automatically parse them to extract Webmention targets and sources, and send Webmentions when new targets are found

## Generic Web framework setup

If you don't use FastAPI or Flask, or you want a higher degree of customization, you can still use the library by implementing and advertising your own Webmentions endpoint, which in turn will simply call `WebmentionsHandler.process_incoming_webmention`.

You will also have advertise the Webmentions endpoint in your responses, either through:

- A `Link` header (with a value in the format `<https://example.com/webmentions>; rel="webmention"`)
- A `<link>` or `<a>` element in the HTML head or body (in the format `<link rel="webmention" href="https://example.com/webmentions">`)

An example is provided [in the documentation](https://git.fabiomanganiello.com/webmentions#generic-setup).

## Generic storage setup

If you don't want to use SQLAlchemy, you can implement your own storage by implementing the `WebmentionsStorage` interface (namely the `store_webmention`, `retrieve_webmentions`, and `delete_webmention` methods), then pass that to the `WebmentionsHandler` constructor.

An example is provided [in the documentation](https://git.fabiomanganiello.com/webmentions#generic-storage).

## Manual handling of outgoing Webmentions

The `FileSystemMonitor` approach is quite convenient if you serve your website (or a least the mentionable parts of it) as static files.

However, if you have a more dynamic website (with posts and comments stored on e.g. a database), or you want to have more control over when Webmentions are sent, you can also call the `WebmentionsHandler.process_outgoing_webmentions` method whenever a post or comment is published, updated or deleted, to trigger the sending of Webmentions to the referenced targets.

An example is provided [in the documentation](https://git.fabiomanganiello.com/webmentions#generic-setup-1).

## Subscribe to mention events

You may want to add your custom callbacks when a Webmention is sent or received - for example to send notifications to your users when some of their content is mentioned, or to keep track of the number of mentions sent by your pages, or to perform any automated moderation or filtering when mentions are processed etc.

This can be easily achieved by providing custom callback functions (`on_mention_processed` and `on_mention_deleted`) to the `WebmentionsHandler` constructor, and both take a single `Webmention` object as a parameter.

An example is provided [in the documentation](https://git.fabiomanganiello.com/webmentions#add-notifications-to-mentions).

## Filtering and moderation

This library is intentionally agnostic about filtering and moderation, but it provides you with the means to implement your own filtering and moderation logic through the `on_mention_processed` and `on_mention_deleted` callbacks.

By default all received Webmentions are stored with `WebmentionStatus.CONFIRMED` status.

This can be changed by setting the `initial_mention_status` parameter of the `WebmentionsHandler` constructor to `WebmentionStatus.PENDING`, which will cause all received Webmentions to be stored but not visible on the website until they are manually confirmed by an administrator.

You can then use the `on_mention_processed` callback to implement your own logic to either notify the administrator of new pending mentions, or to automatically confirm them based on some criteria.

A minimal example is provided [in the documentation](https://git.fabiomanganiello.com/webmentions#filtering-and-moderation).

## Make your pages mentionable

Without good semantic HTML, Webmentions will be quite minimal. They will still work, but they will probably be rendered simply as a source URL and a creation timestamp.

The Webmention specification is intentionally simple, in that the `POST` endpoint only expects a source URL and a target URL. The rest of the information about the mention (the author, the content, the type of mention, any attachments, and so on) is all derived from the source URL, by parsing the HTML of the source page and extracting the relevant Microformats.

While the Microformats2 specification is quite flexible and a work-in-progress, there are a few basic elements whose usage is recommended to make the most out of Webmentions.

A complete example with a semantic-aware HTML article is provided [in the documentation](https://git.fabiomanganiello.com/webmentions/src/branch/main/src/python/examples/semantic-mention-example.html).

## Rendering mentions on your pages

Finally, the last step is to render the received Webmentions on your pages.

A
[`WebmentionsHandler.render_webmentions`](https://docs.webmentions.work/api/webmentions.handlers.html#webmentions.handlers.WebmentionsHandler.render_webmentions)
helper is provided to automatically generate a safe pre-rendered and reasonably
styled (but customizable through CSS variables) `Markup` object, which you can
then render in your templates. Example:

```python
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from webmentions import WebmentionsHandler
from webmentions.server.adapters.fastapi import bind_webmentions

base_url = "https://example.com"
app = FastAPI()
handler = WebmentionsHandler(...)
bind_webmentions(app, handler)

# ...

@app.get("/articles/{article_id}")
def article(request, article_id: int):
    templates = Jinja2Templates(directory="templates")
    mentions = handler.retrieve_webmentions(
      f"{base_url}/articles/{article_id}",
      WebmentionDirection.IN,
    )

    rendered_mentions = handler.render_webmentions(mentions)
    return templates.TemplateResponse(
      "article.html",
      {
        "request": request,
        "article_id": article_id,
        "mentions": rendered_mentions,
      },
    )
```

Where `article.html` is a Jinja template that looks like this:

```html
<!doctype html>
<html>
  <head>
    <title>Example article</title>
  </head>
  <body>
    <main>
      <article class="h-entry">
        <h1 class="p-name">Example article</h1>
        <time class="dt-published" datetime="2026-02-07T21:03:00+01:00">
          Feb 7, 2026
        </time>
        <div class="e-content">
          <p>Your article content goes here.</p>
        </div>
      </article>

      {{ mentions }}
    </div>
  </body>
</html>
```

[More
details](https://git.fabiomanganiello.com/webmentions/#rendering-webmentions)
are provided in the documentation.

For more customizing rendering, a [reference Jinja
template](https://git.fabiomanganiello.com/webmentions/src/branch/main/src/python/examples/mention-render-jinja-example.html)
is also provided in the documentation.

## Current implementations

So far the library is used in [madblog](https://git.fabiomanganiello.com/madblog), a minimal zero-database Markdown-based blogging engine I maintain, which powers both [my personal blog](https://blog.fabiomanganiello.com) and the [Platypush blog](https://blog.platypush.tech).

You can see some Webmentions in action on [some of my blog posts](https://blog.platypush.tech/article/Ultimate-self-hosted-automation-with-Platypush).

And, if you include a link to any article of mine in your website, and your website supports Webmentions (for example [there is a Wordpress plugin](https://wordpress.org/plugins/webmention/)), you should see the mention appear in the comments of the article page. [](https://fed.brid.gy)

## Links

- [PyPi package](https://pypi.org/project/webmentions/)
- [Self-hosted repo](https://github.com/blacklight/webmentions)
- [Github mirror](https://github.com/blacklight/webmentions)
- [API reference](https://docs.webmentions.work/)
