My initial work on a webmention client and receiver for mwop.net
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Matthew Weier O'Phinney eb9b763aac refactor: extrace ProcessWebmentionRequest
Allows consumers to dispatch the WebmentionEvent manually — for instance, if they want to wrap it in a deferable, or handle it immediately.
2026-09-09 10:51:43 -05:00
spec docs: polished webmention receiver spec 2026-09-08 15:35:59 -05:00
src refactor: extrace ProcessWebmentionRequest 2026-09-09 10:51:43 -05:00
test refactor: extrace ProcessWebmentionRequest 2026-09-09 10:51:43 -05:00
.gitattributes qa: gitattributes 2026-09-08 16:56:37 -05:00
.gitignore Initial project setup 2026-09-04 15:20:50 -05:00
composer.json qa: update to latest microformats2 version 2026-09-09 10:27:38 -05:00
composer.lock qa: update to latest microformats2 version 2026-09-09 10:27:38 -05:00
CONTRIBUTING.md docs: adds contributing guide 2026-09-08 12:18:55 -05:00
phpcs.xml.dist Initial project setup 2026-09-04 15:20:50 -05:00
phpunit.xml.dist Initial project setup 2026-09-04 15:20:50 -05:00
README.md docs: usage documentation in README.md 2026-09-08 16:46:29 -05:00

weierophinney/webmention

Library for sending and receiving Webmentions in PHP 8.5+.

Requirements

  • PHP 8.5+
  • A PSR-18 HTTP client (psr/http-client)
  • A PSR-17 HTTP factory (psr/http-factory + psr/http-message)
  • A PSR-14 event dispatcher (psr/event-dispatcher)
  • weierophinney/microformats2

Optional:

  • PSR-3 logger (psr/log) for logging errors and warnings

Installation

composer require weierophinney/webmention

Sending Webmentions

The WebmentionSender sends webmention notifications to a target URL's endpoint.

Usage

use Weierophinney\Webmention\WebmentionSender;
use Uri\WhatWg\Url;

$sender = new WebmentionSender(
    httpClient: $psr18Client,
    requestFactory: $psr17RequestFactory,
);

$sender->send(
    webmentionEndpoint: Url::parse('https://target.example.com/webmention'),
    source: Url::parse('https://source.example.com/my-post'),
    target: Url::parse('https://target.example.com/their-post'),
);

Constructor

Parameter Type Required Description
$httpClient Psr\Http\Client\ClientInterface Yes PSR-18 HTTP client for sending requests
$requestFactory Psr\Http\Message\RequestFactoryInterface Yes PSR-17 factory for creating HTTP requests
$logger Psr\Log\LoggerInterface No PSR-3 logger for warnings on failed sends

Methods

send(Url $webmentionEndpoint, Url $source, Url $target): void

Sends a webmention POST request to the endpoint. Follows up to 5 redirects. Logs a warning if the endpoint returns a non-2XX response.

Receiving Webmentions

Receiving webmentions requires two components: a PSR-15 request handler and an event listener.

WebmentionHandler

A PSR-15 RequestHandlerInterface that validates incoming webmention POST requests and dispatches a WebmentionRequestEvent.

Usage

use Weierophinney\Webmention\Handler\WebmentionHandler;

$handler = new WebmentionHandler(
    responseFactory: $psr17ResponseFactory,
    eventDispatcher: $psr14EventDispatcher,
);

// In your routing layer:
$response = $handler->handle($serverRequest);

Constructor

Parameter Type Required Description
$responseFactory Psr\Http\Message\ResponseFactoryInterface Yes PSR-17 factory for creating HTTP responses
$eventDispatcher Psr\EventDispatcher\EventDispatcherInterface Yes PSR-14 dispatcher for dispatching events

Behavior

  • Returns 405 Method Not Allowed for non-POST requests
  • Returns 400 Bad Request if source or target fields are missing, empty, not valid URLs, or use a scheme other than http/https
  • Dispatches a WebmentionRequestEvent with the validated source and target URLs
  • Returns 202 Accepted on success

WebmountRequestEvent Listener

A callable listener that processes WebmentionRequestEvent by validating the target, fetching the source document, parsing microformats, and classifying the webmention.

Usage

use Weierophinney\Webmention\Event\WebmentionRequestListener;

$listener = new WebmentionRequestListener(
    validateApplicationUrl: function (\Uri\WhatWg\Url $target): bool {
        // Return true if the target URL belongs to your application
        return str_starts_with($target->toAsciiString(), 'https://myapp.example.com/');
    },
    httpClient: $psr18Client,
    requestFactory: $psr17RequestFactory,
    eventDispatcher: $psr14EventDispatcher,
    logger: $psr3Logger, // optional
);

// Register as a PSR-14 listener:
$eventDispatcher->addListener(WebmentionRequestEvent::class, $listener);

Constructor

Parameter Type Required Description
$validateApplicationUrl callable(\Uri\WhatWg\Url): bool Yes Callback to validate that a target URL belongs to your application
$httpClient Psr\Http\Client\ClientInterface Yes PSR-18 HTTP client for fetching source documents
$requestFactory Psr\Http\Message\RequestFactoryInterface Yes PSR-17 factory for creating HTTP requests
$eventDispatcher Psr\EventDispatcher\EventDispatcherInterface Yes PSR-14 dispatcher for dispatching results
$parser Weierophinney\Microformats2\Parser No Microformats2 parser (a new instance is created if omitted)
$logger Psr\Log\LoggerInterface No PSR-3 logger for warnings during processing

Behavior

  1. Invokes the validateApplicationUrl callback with the target URL; returns early if invalid
  2. Issues a HEAD request to the source URL, following up to 5 redirects
  3. Issues a GET request to the resolved source URL
  4. Parses the HTML using weierophinney/microformats2
  5. Classifies the webmention using weierophinney/microformats2's Classifier
  6. Dispatches a WebmentionEvent with the classified WebmentionResult

Events

WebmentionRequestEvent

Property Type Description
$source Uri\WhatWg\Url The source URL of the webmention
$target Uri\WhatWg\Url The target URL of the webmention

WebmentionEvent

Property Type Description
$webmention Weierophinney\Microformats2\Webmention\WebmentionResult The classified webmention result

The $webmention is a subclass of WebmentionResult indicating the type of webmention:

  • Comment — source is in reply to target (u-in-reply-to)
  • Repost — source reposts target (u-repost-of)
  • Like — source likes target (u-like-of)
  • Mention — source mentions target (generic)
  • Deletion — no link to target found in source

Full Example

use Weierophinney\Webmention\Handler\WebmentionHandler;
use Weierophinney\Webmention\Event\WebmentionRequestListener;
use Weierophinney\Webmention\Event\WebmentionEvent;

// Create dependencies (from your DI container)
$psr17ResponseFactory = /* PSR-17 ResponseFactoryInterface */;
$psr18Client          = /* PSR-18 ClientInterface */;
$psr17RequestFactory  = /* PSR-17 RequestFactoryInterface */;
$psr14Dispatcher      = /* PSR-14 EventDispatcherInterface */;
$psr3Logger           = /* PSR-3 LoggerInterface (optional) */;

// Create the handler
$handler = new WebmentionHandler(
    responseFactory: $psr17ResponseFactory,
    eventDispatcher: $psr14Dispatcher,
);

// Create and register the listener
$listener = new WebmentionRequestListener(
    validateApplicationUrl: function (\Uri\WhatWg\Url $target): bool {
        return str_starts_with($target->toAsciiString(), 'https://myapp.example.com/');
    },
    httpClient: $psr18Client,
    requestFactory: $psr17RequestFactory,
    eventDispatcher: $psr14Dispatcher,
    logger: $psr3Logger,
);

$psr14Dispatcher->addListener(
    \Weierophinney\Webmention\Event\WebmentionRequestEvent::class,
    $listener,
);

// Register a handler for classified webmentions
$psr14Dispatcher->addListener(WebmentionEvent::class, function (WebmentionEvent $event): void {
    $result = $event->result;

    match (true) {
        $result instanceof \Weierophinney\Microformats2\Webmention\Comment
            => error_log("Comment received from {$result->source}"),
        $result instanceof \Weierophinney\Microformats2\Webmention\Like
            => error_log("Like received from {$result->source}"),
        $result instanceof \Weierophinney\Microformats2\Webmention\Repost
            => error_log("Repost received from {$result->source}"),
        default
            => error_log("Mention received from {$result->source}"),
    };
});

// Route the webmention endpoint (framework-specific)
// POST /webmention => $handler->handle($request)

Development

Testing

./vendor/bin/phpunit

Coding Standards

./vendor/bin/phpcs
./vendor/bin/phpcbf

License

This library is licensed under the BSD-2-Clause license. See LICENSE for details.