Skip to main content

Framework-native Ko-fi payment webhook handling for Python

Project description

Mofi

Mofi turns Ko-fi payment webhooks into typed Python events inside an existing FastAPI, Flask, or Django application. It validates Ko-fi's shared verification token, preserves the complete payload, and runs application handlers before acknowledging the delivery.

Mofi covers payment notifications only: donations, subscriptions, commissions, and shop orders. Ko-fi does not expose account management, posts, messages, membership cancellations, refunds, or fulfillment operations through this API.

Install

Install only the integration your application uses:

uv add "mofi[fastapi]"
uv add "mofi[flask]"
uv add "mofi[django]"

Use mofi[all] for all three integrations. The equivalent pip install commands also work. A base mofi install includes only Pydantic and the shared event models.

Mofi requires Python 3.10 or newer.

FastAPI

import os

from fastapi import FastAPI

from mofi import Donation, PaymentEvent
from mofi.integrations.fastapi import KoFiRouter

app = FastAPI()
webhook = KoFiRouter(
    os.environ["KOFI_VERIFICATION_TOKEN"],
    prefix="/webhooks/kofi",
)


@webhook.on(Donation)
async def donation(event: Donation) -> None:
    await record_donation(event.message_id, event.amount)


@webhook.on_payment
def every_payment(event: PaymentEvent) -> None:
    audit_payment(event.message_id, event.type)


app.include_router(webhook)

Run this application with your normal ASGI server. Mofi does not create or run one.

Flask

import os

from flask import Flask

from mofi import Donation, PaymentEvent
from mofi.integrations.flask import KoFiBlueprint

app = Flask(__name__)
webhook = KoFiBlueprint(
    "kofi",
    __name__,
    verification_token=os.environ["KOFI_VERIFICATION_TOKEN"],
    url_prefix="/webhooks/kofi",
)


@webhook.on(Donation)
def donation(event: Donation) -> None:
    record_donation(event.message_id, event.amount)


@webhook.on_payment
def every_payment(event: PaymentEvent) -> None:
    audit_payment(event.message_id, event.type)


app.register_blueprint(webhook)

If verification_token is omitted, the Blueprint reads app.config["MOFI_VERIFICATION_TOKEN"]. Explicit configuration takes precedence. Flask's async extra is installed by mofi[flask], so handlers may also be async def and still finish during the request.

Django

import os

from django.urls import path

from mofi import Donation, PaymentEvent
from mofi.integrations.django import KoFiWebhook

webhook = KoFiWebhook(
    verification_token=os.environ["KOFI_VERIFICATION_TOKEN"]
)


@webhook.on(Donation)
def donation(event: Donation) -> None:
    record_donation(event.message_id, event.amount)


@webhook.on_payment
def every_payment(event: PaymentEvent) -> None:
    audit_payment(event.message_id, event.type)


urlpatterns = [
    path("webhooks/kofi/", webhook.view, name="kofi_webhook"),
]

If verification_token is omitted, the view reads the MOFI_VERIFICATION_TOKEN Django setting. The generated external webhook view alone is CSRF-exempt; Mofi adds no application, middleware, model, or migration.

Runnable versions live in examples/.

Events and handler behavior

Known payments use these event classes:

Ko-fi type Mofi class
Donation Donation
Subscription Subscription
Commission Commission
Shop Order ShopOrder

Every event extends PaymentEvent. A future type becomes UnknownPayment instead of being rejected. The complete decoded object is available as event.raw, and newly added fields are also retained by the Pydantic model. Optional supporter messages, tier data, shop fields, shipping, and telephone details may be absent. The Commission model intentionally exposes confirmed common payment fields plus raw; it does not guess undocumented Commission-only fields.

For a known event, Mofi calls handlers registered with .on(...) in registration order, then handlers registered with .on_payment. Unknown events call only .on_payment handlers. Dispatch is sequential. A valid event with no handler is acknowledged with an empty HTTP 200 response.

Mofi returns HTTP 200 only after every selected handler finishes. Parsing and validation failures return a native bad-request response, token mismatch returns a forbidden response, and handler exceptions remain visible to the framework. This lets Ko-fi retry unsuccessful deliveries.

Retries and privacy

Ko-fi can retry a non-200 delivery with the same message_id. Mofi deliberately delivers every attempt; it does not keep state or suppress duplicates. Use event.message_id with your application's durable data store when an operation must be idempotent.

Respect event.is_public before displaying supporter activity. event.raw can contain the verification token, email, supporter message, transaction details, shipping address, and telephone number. Do not log it or return it in responses; apply your application's normal retention and access policies.

Configure Ko-fi securely

  1. Create a high-entropy token in Ko-fi's Webhook settings.
  2. Store the same token in your application's secret configuration.
  3. Set the webhook URL to the HTTPS route mounted above.
  4. Configure request-body limits in your framework or reverse proxy; Mofi keeps that policy with the host application.

Ko-fi puts the token inside the request body. It is a shared-secret check, not a cryptographic request signature, and it cannot prevent replay if the token is compromised. Rotate it in both Ko-fi and your application when exposure is suspected.

Upgrade from 0.2

Version 0.3 removes Mofi's server-owning Mofi class and GlobalType. See the 0.3 migration guide for direct FastAPI replacements and the new Flask and Django integrations.

Development

uv sync --all-extras
uv run pytest
uv run python -m compileall mofi examples
uv build
uv run python tests/verify_distribution.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mofi-0.3.0.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mofi-0.3.0-py3-none-any.whl (11.7 kB view details)

Uploaded Python 3

File details

Details for the file mofi-0.3.0.tar.gz.

File metadata

  • Download URL: mofi-0.3.0.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mofi-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ff87d85ea2eba3317cfa562fff15c4cab8e6094b30679e27f584213230c73f26
MD5 19ee70133e6ed1f2d045c542665a41d7
BLAKE2b-256 230f869975ac78f8d8b9ded2fe8327e41663bd52ee3f61cdda72b4faefbe75b1

See more details on using hashes here.

File details

Details for the file mofi-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: mofi-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 11.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mofi-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ae1a5f14d5e903983ca0ee593988f9f60d70470b1360eff3edb1fe2e65ccc616
MD5 91bc54bc44df9c48e6c4068c0f815ecc
BLAKE2b-256 3cdd5eb1222247f97a1416c09d7ae40f24b38d1cd1d2d6a70e8b9ca345866485

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page