Verified ingress
Knocker keeps durable receipt semantics in SQLite. Bindings adapt host-language request objects and call shared ingress primitives; curated provider verification lives in the SQLite/Rust layer.
Provider tiers
Section titled “Provider tiers”Across the product, provider support has these tiers:
- Curated built-in providers ship in Knocker’s shared provider set and
have repo-owned conformance fixtures under
providers/<name>/. Today:stripe,github,shopify,slack,postmark,resend,paddle,lemon-squeezy,standard-webhooks,clerk,twilio,sendgrid,linear,meta,discord,zendesk,intercom,hubspot,token-header,bearer-token, andbasic-auth. The stringprovider="..."namespace is reserved for these names across bindings. - Binding-local provider extension points live in host-language code. Today this custom-provider object interface exists in the Python binding. Curated providers should still move into shared Rust/SQLite code.
String provider names (provider="...") are reserved for curated
built-ins. Custom providers should not compete in that string namespace.
If you need a project-local hotfix before a provider fix lands in core, use your binding’s local extension point where available and give it a distinct name.
Curating a new provider goes through the Curated provider contribution guide.
Curated provider catalog
Section titled “Curated provider catalog”These names work through the shared SQLite/Rust knocker_receive(...)
path and are available to every binding that exposes receive(...).
| Provider | Verification shape | Notes |
|---|---|---|
stripe | Stripe-Signature timestamped HMAC-SHA256 | Supports overlapping secrets and provider_options={"tolerance_s": 300}. Extracts JSON id and type. |
github | X-Hub-Signature-256: sha256=<hex> over the raw body | Uses X-GitHub-Delivery as the dedupe identity and X-GitHub-Event as the event type. |
shopify | X-Shopify-Hmac-Sha256 base64 HMAC-SHA256 | Uses Shopify webhook/event headers for delivery id, provider event id, and event type. |
slack | X-Slack-Signature over v0:{timestamp}:{body} | Supports timestamp tolerance and extracts event_id / nested event type where present. |
postmark | HTTP Basic Auth | Extracts MessageID and RecordType from JSON bodies. |
resend | Svix/Standard Webhooks signature headers | Expects Svix-style whsec_... secrets and extracts svix-id, data.email_id, and type. |
paddle | Paddle-Signature timestamped HMAC-SHA256 | Extracts event_id and event_type from JSON bodies. |
lemon-squeezy | X-Signature hex HMAC-SHA256 | Extracts the event name plus payload identifier from JSON bodies. |
standard-webhooks | Standard Webhooks/Svix webhook-id, webhook-timestamp, webhook-signature | Also accepts svix as an alias. Expects Svix-style whsec_... secrets. |
clerk | Svix/Standard Webhooks signature headers | Same signature scheme as Standard Webhooks, with Clerk’s webhook headers. |
twilio | X-Twilio-Signature over configured public URL plus sorted form/query params | Requires provider_options={"url": "https://example.com/webhooks/twilio"}. This is the classic Twilio request-validation shape. |
sendgrid | Twilio SendGrid ECDSA Event Webhook signature | Secrets are PEM public keys. Supports timestamp tolerance. |
linear | Linear-Signature HMAC-SHA256 | Supports timestamp tolerance when the body contains webhookTimestamp. |
meta | X-Hub-Signature-256: sha256=<hex> over the raw body | Covers Meta/Facebook-style webhook signatures. |
discord | Ed25519 interaction signature headers | Secrets are Discord public keys. |
zendesk | Timestamped HMAC-SHA256 | Verifies Zendesk’s timestamp plus raw-body signature shape. |
intercom | X-Hub-Signature: sha1=<hex> over the raw body | Extracts id and topic / type from JSON bodies. |
hubspot | HubSpot v3 base64 HMAC-SHA256 over method, URL, body, and timestamp | Requires provider_options={"url": "https://example.com/webhooks/hubspot"}; method defaults to POST. |
token-header | Constant-time comparison of a configured header value | Defaults to X-Knocker-Token; use provider_options={"header": "x-my-secret"} to choose another header. |
bearer-token | Authorization: Bearer <secret> | Useful for Zapier, Make, n8n, and other middleman tools that can set an Authorization header. |
basic-auth | Authorization: Basic <base64(secret)> | Useful when a webhook sender can set Basic Auth but not provider-specific HMAC signatures. |
Every curated provider has repo-owned fixtures under providers/<name>/.
Those fixtures are exercised by Rust core tests and binding mirror tests so
the documented behavior stays tied to executable examples.
Built-in: Stripe
Section titled “Built-in: Stripe”add_endpoint( name="stripe", path="/webhooks/stripe", provider="stripe", secrets=["whsec_old", "whsec_new"], provider_options={"tolerance_s": 300},)The Stripe provider:
- verifies the
Stripe-Signatureheader - accepts overlapping active secrets for rotation
- enforces the configured timestamp tolerance (default
300seconds) - extracts the upstream event id from the JSON body
- extracts the event type from the JSON body
provider_options is schema-checked. Unknown keys raise ValueError at
endpoint registration time so typos do not silently no-op.
Built-in: GitHub
Section titled “Built-in: GitHub”add_endpoint( name="github", path="/webhooks/github", provider="github", secrets=["github-webhook-secret"],)The GitHub provider:
- verifies
X-Hub-Signature-256assha256=<hex hmac>over the raw body - extracts
X-GitHub-Deliveryand uses it as Knocker’s dedupe identity - extracts
X-GitHub-Eventas the event type
GitHub’s X-GitHub-Delivery is stable across operator-initiated redeliveries
from the GitHub dashboard, so Knocker treats a redelivery of the same
delivery id as a duplicate. To force reprocessing, use webhooks.replay(...) on
the stored event instead.
Built-ins for webhook middlemen
Section titled “Built-ins for webhook middlemen”Zapier, Make, n8n, and similar tools often make it easy to send a secret header or Authorization value, but not to compute a provider-specific HMAC over the raw body. Knocker ships three simple curated providers for that shape:
token-header: compares a configurable header, defaulting toX-Knocker-Tokenbearer-token: comparesAuthorization: Bearer <secret>basic-auth: comparesAuthorization: Basic <base64 secret>
add_endpoint( name="zapier", path="/webhooks/zapier", provider="token-header", secrets=["shared-secret"],)Use provider_options={"header": "x-my-secret"} with token-header when
your automation tool already has a fixed header name.
Python Custom Providers
Section titled “Python Custom Providers”The Python binding also exposes a custom-provider object interface for
project-local providers. Implement the small Provider interface and pass
your instance directly to add_endpoint(...). Curated string names like
"stripe", "github",
"shopify", "slack", "postmark", "resend", "paddle",
"lemon-squeezy", "standard-webhooks", "clerk", "twilio",
"sendgrid", "linear", "meta", "discord", "zendesk",
"intercom", "hubspot", "token-header", "bearer-token", and
"basic-auth" are reserved for built-ins; non-curated providers should pass
an instance.
import hmacimport knocker
class AcmeProvider(knocker.Provider): name = "acme" version = "1.0.0" option_keys = frozenset({"clock_skew_s"}) requires_secrets = True
def verify(self, request, *, secrets, options): token = request.header("x-acme-token") delivery_id = request.header("x-acme-delivery") event_type = request.header("x-acme-event") for secret in secrets: if hmac.compare_digest(token or "", secret.decode("utf-8")): return knocker.ProviderResult.accept( provider_delivery_id=delivery_id, event_type=event_type, ) return knocker.ProviderResult.reject( "acme token mismatch", provider_delivery_id=delivery_id, event_type=event_type, )
webhooks = knocker.open("knocker.db")webhooks.add_endpoint( name="acme", path="/webhooks/acme", provider=AcmeProvider(), secrets=["acme-secret"],)Python community providers ship as ordinary packages that expose a
Provider value. Apps register them at add_endpoint(...) time:
import knockerimport acme_webhooks # third-party community package
webhooks.add_endpoint( name="acme", path="/webhooks/acme", provider=acme_webhooks.provider(), secrets=["acme-secret"],)Providers passed as instances are scoped to that endpoint. They do not
appear in provider_versions(), which only reflects the curated
built-ins. There is no global string-lookup registry for non-curated
providers in Knocker; the instance path is the only path.
Provider.verify(...) returns a ProviderResult that combines the
verification outcome and the metadata the provider could extract.
Extraction should happen before the signature check so an invalid receipt
still produces a useful orphan delivery row with provider delivery id,
event id, and event type populated where available.
ProviderRequest exposes case-insensitive request.header(name) and
request.json(). request.json() raises ValueError on non-JSON bodies
and caches its parsed result; guard with try/except for endpoints that
may receive non-JSON payloads.
If your Provider.verify(...) raises an unexpected exception, Knocker
turns that into a verification failure with a useful signature_error
and stores an orphan delivery rather than crashing the caller.
Provider Registry Inspection
Section titled “Provider Registry Inspection”provider_versions()Bindings may expose provider-version inspection for debugging and release notes. Provider versions are not part of any Knocker compatibility contract.
Generic HMAC verification
Section titled “Generic HMAC verification”Generic HMAC has too many per-provider knobs (header name, prefix,
secret format) to ship as a curated provider without diluting the
support promise. It stays available as the explicit
verification={...} config path:
add_endpoint( name="acme", path="/webhooks/acme", verification={ kind="hmac-sha256", header="x-acme-signature", prefix="sha256=", secrets=["old-secret", "new-secret"], }, delivery_key=<read x-acme-delivery-id>, event_key=<read x-acme-event-id>,)The legacy verification config:
kind="stripe"(kept for compatibility; the Stripe provider runs underneath)kind="hmac-sha256"(also acceptsgeneric-hmac-sha256andhmac)- one
secretor manysecrets - optional
prefixfor generic HMAC - optional
tolerance_sfor Stripe
Generic HMAC signs only the request body. Use a provider-specific verifier or custom verification path when you need timestamp-based replay protection.
Verification outcomes
Section titled “Verification outcomes”Every inbound receipt becomes a Delivery, including invalid signatures.
- valid receipt: stores a
Delivery, creates or correlates anEvent, and may enqueue work - invalid receipt: stores a
Deliveryonly, withevent_id=None
Invalid orphan deliveries still surface provider-extracted metadata where the provider was able to read it before the signature check failed, so you can inspect bad receipts without losing them.
Correlation keys
Section titled “Correlation keys”Knocker separates delivery-level identity from event-level identity:
delivery_keyidentifies one upstream HTTP receiptevent_keyidentifies one upstream business event
Built-in providers fill these in automatically. For app-local providers,
populate them in your ProviderResult. You can also pass delivery_key=...
or event_key=... to add_endpoint to override what the provider
extracted; explicit receive(...) arguments win over both.
When to use ingest(...)
Section titled “When to use ingest(...)”Most users should call receive(...).
ingest(...) is the lower-level contract entry point when you already
know the verification result and extracted metadata and want to drive the
durable rows directly. It is trusted ingress: passing signature_valid=True
means Knocker will store and enqueue as valid without running a verifier.