Contributing a curated provider
A curated provider is one that ships in Knocker’s shared provider set and is
supported as a named provider (provider="stripe", provider="github", …)
across the SQLite extension and language bindings. Curated providers carry a
support promise; the bar is intentionally higher than for binding-local
custom providers.
If you only need a provider for your own app, use the binding-local provider extension point instead. Curate a provider only when it is broadly useful and you are willing to help maintain it.
What lives in the repo
Section titled “What lives in the repo”Each curated provider has a directory under providers/<name>/ at the
repo root:
providers/ stripe/ metadata.json fixtures/ valid.json invalid_signature.json missing_signature_header.json expired_timestamp.json github/ metadata.json fixtures/ valid.json invalid_signature.json missing_signature_header.json missing_delivery_header.jsonThe catalog is repo-owned conformance material for the shared provider contract. Runtime plugin loading and auto-discovery are intentionally deferred; curated providers are implemented in shared core code rather than generated separately for each binding.
metadata.json schema
Section titled “metadata.json schema”{ "name": "stripe", "version": "1.0.0", "support_tier": "curated", "description": "Stripe webhook signature verification ...", "upstream_docs_url": "https://stripe.com/docs/webhooks/signatures"}name— lowercase identifier; must match the bundled implementation’sProvider.name. Reserved against app-local / community providers.version— implementation SemVer (e.g.,"1.0.0"). Bump when the provider’s behavior or fixture contract changes.support_tier— always"curated"for entries in this catalog.description— one-paragraph English description of what the provider verifies and extracts.upstream_docs_url— optional link to the provider’s webhook docs.
Fixture JSON schema
Section titled “Fixture JSON schema”Each fixture file describes one request and the expected outcome. Fixtures are binding-neutral: they describe inputs and assertions, not Python specifics.
{ "description": "Valid Stripe signature with fixture-frozen timestamp ...", "request": { "method": "POST", "headers": { "Stripe-Signature": "t=1714000000,v1=89a4..." }, "query": {}, "body": "{\"id\":\"evt_1\",\"type\":\"checkout.session.completed\"}", "secrets": ["whsec_test"], "now_s": 1714000000 }, "expected": { "signature_valid": true, "provider_event_id": "evt_1", "event_type": "checkout.session.completed" }}Request fields
method— HTTP method, default"POST".headers— string-to-string map. Case is preserved as written; provider implementations look up headers case-insensitively.query— string-to-string map.body— UTF-8 string. The conformance loader encodes to bytes.secrets— list of UTF-8 strings. Shared SQLite receive tests pass these values through JSON toknocker_receive(...); binding mirror tests use the same fixture values.provider_options— optional dict matching the provider’s runtimeprovider_options=...shape. Shared SQLite receive consumes this JSON option shape.now_s— optional integer seconds. Tests use this for timestamped providers so fixtures remain stable instead of silently rotting as wall-clock drifts.
Expected fields
signature_valid— required boolean.signature_error_contains— optional substring expected inProviderResult.signature_errorfor rejected fixtures.provider_delivery_id,provider_event_id,event_type— optional exact-match assertions on the extracted metadata. Invalid receipts should still surface metadata that was extractable before signature failure, so an invalid fixture should normally still assert the metadata fields.
Coverage expectations
Section titled “Coverage expectations”Each curated provider must include fixtures for at least:
- valid signature
- invalid signature
- required-header missing case
- metadata extraction outcome (covered by the above fixtures’
expected) - any provider-specific replay/timestamp case Knocker promises in docs
(e.g., Stripe
expired_timestamp.json)
Passing the fixture suite does not mean the implementation is bug-free.
Binding-specific edge-case tests still belong alongside the bundled
provider in tests/test_providers.py and friends.
Behavior changes are contract changes
Section titled “Behavior changes are contract changes”If you change a curated provider’s verification or extraction behavior in
any way that updates a fixture’s expected field, that is a contract
change, not routine churn. The PR description should explain why the
behavior is changing and which downstream consumers are affected. Do
not rubber-stamp fixture diffs to make tests pass.
What is intentionally out of scope
Section titled “What is intentionally out of scope”- runtime plugin loading, native plugin ABI, WASM
- automatic discovery of providers from installed packages
- per-language provider implementations or code generation (bindings use the shared SQLite provider verification path for curated providers)
- provider deprecation/sunset policy (handled per-release in CHANGELOG if and when needed)
The curated catalog exists to make conformance and contribution reviewable without committing to a plugin operating system.