Skip to content

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.

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.json

The 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.

{
"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’s Provider.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.

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 to knocker_receive(...); binding mirror tests use the same fixture values.
  • provider_options — optional dict matching the provider’s runtime provider_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 in ProviderResult.signature_error for 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.

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.

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.

  • 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.