Quick start
This page shows the whole Knocker shape once:
- open a SQLite database
- register a webhook endpoint
- receive an HTTP webhook into durable rows
- run a worker against the same SQLite file
- handle the stored event inside Knocker’s transaction
The examples default to Python because it is compact, but every tab is the same product flow over the same SQLite extension.
Basic Ideas
Section titled “Basic Ideas”Knocker stores every receipt as a Delivery. Valid receipts create or
correlate to an Event. Workers process events later, either in the same
process as your HTTP route or from another process opening the same SQLite
file.
Handlers receive a stored event plus a transaction handle. Business writes through that transaction commit atomically with Knocker’s handled transition and queue acknowledgement. If the handler fails, those writes roll back before Knocker records retry/dead-letter state.
For the deeper mental model, read Concepts after this page.
Pick A Runtime
Section titled “Pick A Runtime”Start with the binding for the runtime you actually use:
Minimal Example
Section titled “Minimal Example”import asyncioimport knocker
webhooks = knocker.open("app.db")automation = webhooks.endpoint( "automation", path="/webhooks/automation", provider="token-header", secrets=["dev-secret"],)
@automation.handle("invoice.created")def handle_invoice(event, tx): print("handled", event.id)
result = automation.receive( body=b'{"id":"evt_1","type":"invoice.created"}', headers={"X-Knocker-Token": "dev-secret"},)
stop = asyncio.Event()worker = asyncio.create_task(webhooks.run_worker(stop_event=stop))await asyncio.sleep(0.1)stop.set()await worker-- Load the extension using your SQLite client, then bootstrap Knocker..load ./target/release/libknocker_extSELECT knocker_bootstrap();
SELECT knocker_endpoint_upsert( 'automation', '/webhooks/automation', 'token-header', 1);
SELECT knocker_receive( 'automation', 'token-header', json_array('dev-secret'), json_object(), 'POST', json_object('X-Knocker-Token', 'dev-secret'), CAST('{"id":"evt_1","type":"invoice.created"}' AS BLOB), json_object(), NULL, NULL, NULL, NULL, 'knocker.events', 3) AS result_json;
SELECT e.id, ep.name AS endpoint, e.event_type, e.statusFROM knocker_events eJOIN knocker_endpoints ep ON ep.id = e.endpoint_id;import { KnockerSqlite } from "./packages/knocker-bun/index";
const webhooks = new KnockerSqlite("app.db");
webhooks.addEndpoint({ name: "automation", path: "/webhooks/automation", provider: "token-header", secrets: ["dev-secret"],});
webhooks.registerHandler("automation", (event, tx) => { console.log("handled", event.id);}, "invoice.created");
const result = webhooks.receive({ endpoint: "automation", body: Buffer.from('{"id":"evt_1","type":"invoice.created"}'), headers: { "X-Knocker-Token": "dev-secret" },});
await webhooks.runWorker({ workerId: "bun-worker", maxJobs: 1 });{:ok, opened} = KnockerSqlite.open("app.db")
webhooks = KnockerSqlite.register_handler(opened, "automation", "invoice.created", fn event, _tx -> IO.inspect(event["id"], label: "handled") end)
{:ok, _endpoint_id} = KnockerSqlite.add_endpoint(webhooks, %{ name: "automation", path: "/webhooks/automation", provider: "token-header", enabled: true })
{:ok, result} = KnockerSqlite.receive(webhooks, %{ endpoint: "automation", provider: "token-header", secrets: ["dev-secret"], body: ~s({"id":"evt_1","type":"invoice.created"}), headers: %{"X-Knocker-Token" => "dev-secret"} })
1 = KnockerSqlite.run_worker(webhooks, "elixir-worker", %{max_jobs: 1})webhooks, err := knockersqlite.Open("app.db")if err != nil { panic(err)}defer webhooks.Close()
provider := "token-header"_, err = webhooks.AddEndpoint(knockersqlite.AddEndpointParams{ Name: "automation", Path: "/webhooks/automation", Provider: &provider, Enabled: true, Secrets: []string{"dev-secret"},})if err != nil { panic(err)}
webhooks.RegisterEventHandler("automation", "invoice.created", func(event knockersqlite.Event, tx *knockersqlite.Tx) error { fmt.Println("handled", event.ID) return nil})
result, err := webhooks.Receive(knockersqlite.ReceiveParams{ Endpoint: "automation", Body: []byte(`{"id":"evt_1","type":"invoice.created"}`), Headers: map[string]any{"X-Knocker-Token": "dev-secret"},})if err != nil { panic(err)}
_, err = webhooks.RunWorker(context.Background(), "go-worker", 1)import { KnockerSqlite } from "./packages/knocker-node/index.mjs";
const webhooks = new KnockerSqlite("app.db");
webhooks.addEndpoint({ name: "automation", path: "/webhooks/automation", provider: "token-header", secrets: ["dev-secret"],});
webhooks.registerHandler("automation", "invoice.created", (event, tx) => { console.log("handled", event.id);});
const result = webhooks.receive({ endpoint: "automation", body: Buffer.from('{"id":"evt_1","type":"invoice.created"}'), headers: { "X-Knocker-Token": "dev-secret" },});
await webhooks.runWorker({ workerId: "node-worker", maxJobs: 1 });require_relative "./packages/knocker-ruby/lib/knocker_sqlite"
webhooks = KnockerSqlite::Database.open("app.db")
webhooks.add_endpoint( name: "automation", path: "/webhooks/automation", provider: "token-header", secrets: ["dev-secret"],)
webhooks.register_handler("automation", event_type: "invoice.created") do |event, tx| puts "handled #{event['id']}"end
result = webhooks.receive( endpoint: "automation", body: "{\"id\":\"evt_1\",\"type\":\"invoice.created\"}", headers: { "X-Knocker-Token" => "dev-secret" },)
webhooks.run_worker(worker_id: "ruby-worker", max_jobs: 1)What To Read Next
Section titled “What To Read Next”- Concepts for the model behind deliveries, events, workers, and retries.
- Verified ingress for provider names, secrets, and options.
- Frameworks and ORMs for route glue and transaction patterns.
- Operator surface for reads, replay, requeue, ignore, and delivery replay.
- Retention and pruning for manual and automated cleanup.