@ewanc26/supporters
@ewanc26/supporters is a SvelteKit component library for displaying Ko-fi supporters and GitHub Sponsors. Webhook events from both platforms are stored as records on your ATProto PDS and aggregated into presentable supporter lists.
Part of the @ewanc26/pkgs monorepo.
How it works
Ko-fi
- Ko-fi POSTs a webhook event to /webhook on each transaction
- The handler verifies the verification_token, respects is_public, and calls appendEvent
- appendEvent writes a record to your PDS under uk.ewancroft.support.kofi
- readStore fetches all records and aggregates them into KofiSupporter objects
- Pass the result to or
GitHub Sponsors
- GitHub POSTs a sponsorship webhook event to /webhook/github on each sponsorship change
- The handler verifies the HMAC-SHA256 signature, respects privacy_level, and skips pending_* actions
- appendSponsorEvent writes a record to your PDS under uk.ewancroft.support.github
- readSponsors fetches all records and replays them chronologically to derive the current state (active/inactive, current tier) per sponsor
- Pass the result to
Install
Requires svelte >= 5 and @atproto/api >= 0.13.0 as peer dependencies.
Setup
Environment variables
Generate an app password under Settings โ App Passwords on witchsky.
Register the Ko-fi webhook
Set your webhook URL to https://your-domain.com/webhook in ko-fi.com/manage/webhooks.
Register the GitHub Sponsors webhook
In your GitHub Sponsors settings, add a webhook pointing to https://your-domain.com/webhook/github. Set the content type to application/json, choose a secret, and subscribe to Sponsorship events only.
Add the routes
Copy src/routes/webhook/+server.ts and src/routes/webhook/github/+server.ts from the package into your SvelteKit app's routes directory.
Use the components
Components
Displays all Ko-fi supporters with emoji type badges (โ donation, โญ subscription, ๐จ commission, ๐๏ธ shop order).
| Prop | Type | Default |
|---|---|---|
| supporters | KofiSupporter[] | [] |
| heading | string | 'Supporters' |
| description | string | 'People who support my work on Ko-fi.' |
| filter | KofiEventType[] | undefined (show all) |
| loading | boolean | false |
| error | string | null | null |
Convenience wrapper around pre-filtered to Subscription events only.
Displays GitHub Sponsors with their tier name. Each card links to the sponsor's GitHub profile.
| Prop | Type | Default |
|---|---|---|
| sponsors | GitHubSponsor[] | [] |
| heading | string | 'GitHub Sponsors' |
| description | string | 'People who sponsor my work on GitHub.' |
| activeOnly | boolean | true |
| loading | boolean | false |
| error | string | null | null |
Server utilities
Ko-fi
readStore(): Promise<KofiSupporter[]>
Fetches all uk.ewancroft.support.kofi records from the PDS (no auth required) and aggregates them by name into KofiSupporter objects. Reads are paginated automatically.
appendEvent(name, type, tier, timestamp, opts?): Promise
Writes a single Ko-fi event as a new record. Uses ATPROTO_APP_PASSWORD for authentication. The rkey is a TID derived from the transaction timestamp via @ewanc26/tid.
parseWebhook(request, opts?): Promise
Validates and parses an incoming Ko-fi application/x-www-form-urlencoded webhook request. Throws WebhookError on invalid token, wrong content-type, or malformed JSON.
GitHub Sponsors
fetchSponsorEvents(did): Promise<GitHubSponsorEvent[]>
Fetches all uk.ewancroft.support.github records from the PDS (no auth required) and returns them as a flat chronological timeline โ one entry per event, sorted most-recent-first. Used by the website's unified supporters feed.
readSponsors(): Promise<GitHubSponsor[]>
Fetches all uk.ewancroft.support.github records from the PDS (no auth required) and replays them chronologically to produce the current state per sponsor. A sponsor is considered active if their most recent event is created or tier_changed, and inactive after cancelled.
appendSponsorEvent(login, name, action, tierName, monthlyUsd, timestamp): Promise
Writes a single GitHub sponsorship event as a new PDS record. Uses ATPROTO_APP_PASSWORD for authentication.
parseGitHubSponsorsWebhook(request, opts?): Promise
Validates an incoming GitHub sponsorship webhook request by verifying its HMAC-SHA256 signature against GITHUB_WEBHOOK_SECRET using the Web Crypto API. Throws GitHubWebhookError on signature mismatch, wrong event type, or malformed JSON.
Types
Importing historical Ko-fi data
Export your transaction history from ko-fi.com/manage/transactions โ Export CSV, then run the bundled import script:
Remove --dry-run to write records. The script is idempotent โ re-running merges new event types and tiers into existing records.
Lexicons
uk.ewancroft.support.kofi
uk.ewancroft.support.github
rkeys are TIDs derived from the event timestamp, making all records lexicographically sortable by time.
Licence
AGPL-3.0-only โ see the pkgs monorepo.
Discussion in the ATmosphere