Skip to main content

Module webhook

Module webhook 

Source
Expand description

Webhook capability trait. See plan/ecosystem/02-capabilities.md (Webhook section).

A WebhookPlugin verifies that an inbound HTTP request really was sent by a given third party. The shape is deliberately vendor-neutral: Stripe’s Stripe-Signature, GitHub’s X-Hub-Signature-256, Shopify’s X-Shopify-Hmac-Sha256, Slack’s v0:{ts}:{body} scheme and Twilio’s URL + sorted-params scheme all satisfy the same surface, and a project can swap implementations by editing bext.config.toml without touching code.

§Design notes

  • Verify-only. The trait does not carry a handle(event) method. In bext, webhook handlers are normal routes — the value of the capability is the per-vendor signature check that must run before the route body touches the payload. Each project’s business logic for “what happens when Stripe reports a charge” lives in the route, not the plugin, and the plan’s original handle(event) sketch would have forced every verifier crate to carry a decoder for every vendor’s event shape — an open-ended commitment that violates architecture principle 6 (no vendor-specific fields on the trait). Leaving the plugin as a pure verifier also matches the existing AuthPlugin::resolve pattern: a trait that answers one well-posed question and hands the result back to middleware.

  • Sync, not async. Every other trait in this crate is sync (middleware.rs, auth.rs, session.rs, mailer.rs, tracer.rs, scheduled.rs). All five of the in-scope verifiers are pure in-memory HMAC — no network I/O, nothing to await. Matching the existing convention keeps the WASM/QuickJS/nsjail ABI consistent and avoids dragging async-trait into a dependency-minimal leaf crate. Future verifiers that need network I/O (OAuth-style signature checks with a JWKS fetch) bridge with block_on the same way SesMailerPlugin does today.

  • Errors classify, don’t decorate. Callers need the variant to pick an HTTP status: MissingHeader/MalformedPayload → 400, InvalidSignature/ReplayDetected → 401, Backend → 500. The inner String is vendor-provided detail for logs. Do not parse it. This mirrors the MailerError shape from E1.

  • Raw bytes, not a structured event. WebhookRequest::body is a Vec<u8> because every vendor’s signature covers the exact byte stream on the wire — JSON reserialisation would mutate whitespace and break the HMAC. Headers are Vec<(String, String)> rather than a map because multiple headers with the same name are legal in HTTP and preserving order aids reproducibility in logs.

Structs§

WebhookRequest
A minimal, ABI-flat snapshot of the inbound HTTP request that a WebhookPlugin needs in order to verify the signature.

Enums§

WebhookError
Why a webhook verification failed, classified so callers can map the variant onto an HTTP response code.
WebhookSchemeKind
What kind of signature scheme a provider implements. Runtime uses this to surface shape in admin UI and to help CLI wizards generate the right secret-configuration stanza. It is not an exhaustive taxonomy — new schemes land as new variants.

Traits§

WebhookPlugin
A plugin that verifies webhook requests from one third-party provider.