A (wip) Cloudflare Worker to handle IndieWeb services for my websites.
indieweb posse webmentions indieauth xmlrpc jekyll
TypeScript 100.0%
1 1 0

Clone this repository

https://tangled.org/arthr.me/indieweb-worker https://tangled.org/did:plc:aeaouj6eedwqmk4z3pies55n/indieweb-worker
git@tangled.org:arthr.me/indieweb-worker git@tangled.org:did:plc:aeaouj6eedwqmk4z3pies55n/indieweb-worker

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

IndieWeb Worker#

A Cloudflare Worker that adds IndieWeb capabilities to a static Jekyll site hosted on Cloudflare Pages. It provides an IndieAuth / OAuth 2.0 server, a webmention receiver, and a POSSE (Publish on your Own Site, Syndicate Elsewhere) pipeline that syndicates posts to Mastodon, Bluesky, Tumblr, and Flickr on each deploy.


Prerequisites#


Deploy#

1. Clone and install#

git clone https://github.com/arthr/indieweb-worker
cd indieweb-worker
npm install

2. Set vars in wrangler.toml#

Edit the [vars] section:

[vars]
SITE_URL = "https://yourblog.com"
NOTES_CATEGORY = "notes"   # Jekyll category for short/note posts
PHOTOS_CATEGORY = "photos" # Jekyll category for photo posts

3. Set secrets#

Run each command and paste the value when prompted:

Secret Description
AUTH_PASSPHRASE Password shown on the IndieAuth consent screen
DEPLOY_HOOK_SECRET Shared secret between Pages deploy hook and this worker
WEBMENTION_IO_TOKEN Token from webmention.io to authenticate inbound webhooks
BLUESKY_HANDLE Your Bluesky handle (e.g. you.bsky.social)
BLUESKY_APP_PASSWORD Bluesky App Password
MASTODON_INSTANCE_URL Your Mastodon instance URL (e.g. https://mastodon.social)
MASTODON_ACCESS_TOKEN Mastodon access token with write:statuses scope
TUMBLR_OAUTH_TOKEN Tumblr OAuth 1.0a access token
TUMBLR_BLOG_IDENTIFIER Your Tumblr blog name (e.g. yourblog.tumblr.com)
FLICKR_API_KEY Flickr API key
FLICKR_API_SECRET Flickr API secret
FLICKR_OAUTH_TOKEN Flickr OAuth access token
FLICKR_OAUTH_TOKEN_SECRET Flickr OAuth access token secret
wrangler secret put AUTH_PASSPHRASE
wrangler secret put DEPLOY_HOOK_SECRET
wrangler secret put WEBMENTION_IO_TOKEN
# ... (repeat for each secret above)

4. Deploy#

npx wrangler deploy

Configure your Jekyll site#

1. Get your configuration snippets#

curl https://your-worker.workers.dev/setup

The response includes ready-to-use snippets keyed to your worker's URL.

Copy the head_tags.snippet value from the /setup response and paste it into your _includes/head.html (or equivalent):

<link rel="authorization_endpoint" href="https://your-worker.workers.dev/auth">
<link rel="token_endpoint" href="https://your-worker.workers.dev/auth/token">
<link rel="indieauth-metadata" href="https://your-worker.workers.dev/.well-known/oauth-authorization-server">
<link rel="webmention" href="https://your-worker.workers.dev/webmentions/receive">

3. Add feed.json#

Create feed.json in your Jekyll root with the JSON Feed layout. The POSSE pipeline reads this feed on deploy.

Required fields per item:

Field Description
id Canonical URL of the post
url Same as id for most posts
tags Array of category/tag strings (used to route to the right silo)
attachments Array of { url, mime_type } objects for photo posts

4. Register the webmention endpoint#

Go to webmention.io, sign in with your domain, and add your webmention endpoint:

https://your-worker.workers.dev/webmentions/receive

5. Add the Pages deploy hook#

In Cloudflare Dashboard → Pages → your project → Settings → Deploy Hooks, create a new hook. Then go to the same project's Environment Variables and add:

DEPLOY_HOOK_URL = https://your-worker.workers.dev/posse/deploy-hook?secret=<your DEPLOY_HOOK_SECRET>

Configure Pages to curl -X POST $DEPLOY_HOOK_URL as a post-deploy step, or use the URL directly as a Pages deploy hook.


Endpoint reference#

Method Path Auth Description
GET /.well-known/oauth-authorization-server None IndieAuth / OAuth 2.0 server metadata
GET /setup None Configuration guide with snippets for your site
GET /health None Service health check
GET /auth None IndieAuth consent page
POST /auth None Exchange auth code for profile (no scopes)
POST /auth/approve Passphrase Submit consent form, issue auth code
POST /auth/token None Exchange auth code for access token
POST /auth/token/introspect None Validate and introspect a token
POST /auth/token/revoke None Revoke a token
POST /webmentions/receive WEBMENTION_IO_TOKEN Receive a webmention from webmention.io
GET /webmentions?target=<url> None List webmentions for a URL
GET /webmentions/:id None Get a single webmention
DELETE /webmentions/:id Bearer (delete scope) Delete a webmention
POST /posse/deploy-hook?secret=<s> DEPLOY_HOOK_SECRET Syndicate new posts to all silos
GET /posse/status/:id Bearer (create scope) Get syndication status for a post

Local development#

Create .dev.vars in the project root (never commit this file):

SITE_URL=http://localhost:8787
AUTH_PASSPHRASE=local-password
DEPLOY_HOOK_SECRET=local-secret
WEBMENTION_IO_TOKEN=local-token
BLUESKY_HANDLE=you.bsky.social
BLUESKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
MASTODON_INSTANCE_URL=https://mastodon.social
MASTODON_ACCESS_TOKEN=your-token
TUMBLR_OAUTH_TOKEN=your-token
TUMBLR_BLOG_IDENTIFIER=yourblog.tumblr.com
FLICKR_API_KEY=your-key
FLICKR_API_SECRET=your-secret
FLICKR_OAUTH_TOKEN=your-token
FLICKR_OAUTH_TOKEN_SECRET=your-secret

Then start the dev server:

npm run dev

Verify the setup endpoint:

curl http://localhost:8787/setup