Bounded ETS-backed dedup cache for webhook deliveries.
Vendors retry deliveries on non-2xx responses (Stripe for 72h, Slack continuously). When a registration opts into idempotency, this module remembers the delivery key for a TTL window so repeat deliveries return 200 without re-dispatching to the agent.
Key sources supported in the registration config:
%{"header" => "x-github-delivery"}— read from request headers.%{"json_path" => "$.id"}— read from the raw body interpreted as JSON (top-level field only; full JSONPath is out of scope for v1).
Entries are swept lazily on access — each check/3 call checks the
stored expires_at and treats an expired entry as first-sight.
Summary
Functions
Checks whether key has been seen within the TTL window.
Returns a specification to start this module under a supervisor.
Extracts the idempotency key from the request per the registration's
key config. Returns :no_key when idempotency is not configured,
or {:error, :missing} when configured but the key is absent.
Types
@type config() :: %{ optional(:key) => %{optional(String.t()) => String.t()}, optional(:ttl) => pos_integer() }
Functions
Checks whether key has been seen within the TTL window.
Returns :ok on first sight (and records the key) or :duplicate
if the key is still valid.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec extract_key(Plug.Conn.t(), config() | nil) :: {:ok, String.t()} | :no_key | {:error, :missing}
Extracts the idempotency key from the request per the registration's
key config. Returns :no_key when idempotency is not configured,
or {:error, :missing} when configured but the key is absent.
@spec start_link(keyword()) :: GenServer.on_start()