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#
- A Cloudflare account with Workers enabled
- Wrangler CLI (
npm install -g wrangler) - A Jekyll site deployed to Cloudflare Pages
- A
feed.json(JSON Feed) in your Jekyll site's root (see Configure your Jekyll site)
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.
2. Add <link> tags to <head>#
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