/** * Signing an outbound event, and checking one on the way in. * * Both halves live here because a receiver and a sender that disagree fail **silently in one * direction** — the same shape as `PURGE_PATH` and `searchTokens`, and the reason those are shared * rather than copied. A site whose verification is subtly different from Taproot's signing does not * see wrong events; it sees *no* events, with a 401 in a log nobody is watching, on the deployment * where the CMS reports every delivery as failed. * * Re-exported from `pure.ts`, so a consumer can verify without pulling Kysely into its bundle. * Nothing here touches the database, and nothing here may start to. */ /** The signature itself: `t=,v1=`. */ export declare const WEBHOOK_SIGNATURE_HEADER = "x-taproot-signature"; /** The event name, so a receiver can route without parsing the body. */ export declare const WEBHOOK_EVENT_HEADER = "x-taproot-event"; /** * The delivery id, which is what makes a retry safe to receive twice. * * At-least-once is the only delivery guarantee an HTTP retry can offer: a request that times out * after the receiver committed is indistinguishable from one that never arrived, so the sweep will * send it again. Stable across every attempt of one delivery — it is the row's primary key — so a * consumer that records ids it has processed gets exactly-once for the cost of one lookup. */ export declare const WEBHOOK_DELIVERY_HEADER = "x-taproot-delivery"; /** * How far out of date a signature may be, in seconds. * * A timestamp is what bounds replay: without one, a body-only signature stays valid forever, so an * intercepted "published" event can be re-sent at any time to whatever the receiver does with it. * Five minutes is the usual figure and is generous enough for clock drift between two deployments * that never talk about time. * * It also settles a thing about retries: the signature is computed **per attempt**, never stored. * A delivery queued and retried eight hours later signs with the clock at the moment it is sent, so * a long backoff cannot make Taproot's own retry look like an attack. */ export declare const WEBHOOK_TIMESTAMP_TOLERANCE = 300; /** * The header value for a body, at a moment. * * **The timestamp is inside the signed message, not merely beside it.** Signing the body alone and * sending `t=` next to it is the version that looks identical and is worthless: an attacker replays * the captured body with a fresh `t`, the tolerance check passes because the timestamp is current, * and the signature still verifies because nothing about it depended on the timestamp. * * `timestamp` is injectable for tests only. Callers pass nothing. */ export declare function signWebhook(secret: string, body: string, timestamp?: number): Promise; export interface VerifyWebhookOptions { /** The shared secret, as shown once when the endpoint was created. */ secret: string; /** * The **raw** request body. * * Not a parsed object re-serialised: `JSON.stringify(await request.json())` reorders nothing today * and is still not the bytes that were signed the first time a value round-trips differently — a * large integer, a lone surrogate, a key order some runtime does not preserve. Read * `await request.text()` and parse afterwards. */ body: string; /** The `x-taproot-signature` header, verbatim. */ header: string | null | undefined; /** Seconds of clock skew allowed. Defaults to `WEBHOOK_TIMESTAMP_TOLERANCE`. */ toleranceSeconds?: number; /** Injected in tests; the wall clock otherwise. Unix seconds. */ now?: number; } /** * Whether a request really came from the Taproot deployment holding this secret. * * One boolean, and deliberately no reason: "wrong signature", "too old" and "malformed header" are * the same answer to whoever is probing, exactly as `resolvePreviewToken` answers `undefined` to * absent, unknown and expired alike. */ export declare function verifyWebhookSignature(options: VerifyWebhookOptions): Promise;