POSTs each event as JSON to a URL.
Options
:url— required. Must behttporhttps.:secret— signs the body. Strongly recommended; see below.:headers— extra headers, as{name, value}string pairs.:transport— aKepler.Transport. Defaults toKepler.Transport.Httpc.:transport_opts— passed through to the transport. The default one accepts:timeout,:connect_timeout, and:ssl.
config :kepler,
sinks: [
{Kepler.Sink.Webhook,
url: System.fetch_env!("KEPLER_WEBHOOK_URL"),
secret: System.fetch_env!("KEPLER_WEBHOOK_SECRET"),
headers: [{"x-team", "payments"}]}
]Headers
Every request carries kepler-watch, kepler-event-id, kepler-severity,
and kepler-schema, so a consumer can route without parsing the body.
Signing
With a :secret, requests carry:
kepler-signature: t=1754481296,v1=<hex>where the hex is HMAC-SHA256(secret, "<t>.<raw body>"). Verify against the
raw body, before any JSON parsing, and reject timestamps outside a few
minutes of now to blunt replays. In Elixir:
def verify(secret, signature_header, raw_body) do
["t=" <> timestamp, "v1=" <> digest] = String.split(signature_header, ",")
expected = Kepler.Sink.Webhook.signature(secret, timestamp, raw_body)
Plug.Crypto.secure_compare(digest, expected) and fresh?(timestamp)
endUse a constant-time comparison — Plug.Crypto.secure_compare/2 or
:crypto.hash_equals/2 — not ==.
Failure
A non-2xx response or a transport error counts as a failed delivery and the
event is dropped. There is no retry: Kepler will not retry into a wedged
cluster, and it has no durable queue to retry from. Kepler.status/0 reports
the drop count.
Summary
Functions
Turns the event into the JSON body and the routing fields the headers need.
The signature Kepler sends for body at timestamp.
Functions
@spec format(Kepler.Event.t()) :: %{ body: iodata(), id: String.t(), watch: String.t(), severity: String.t() }
Turns the event into the JSON body and the routing fields the headers need.
Separated from deliver/2 so the wire format is replaceable without
reimplementing the transport, signing, and failure handling around it.
The signature Kepler sends for body at timestamp.
Exposed so an Elixir consumer can verify without reimplementing the scheme. Compare the result in constant time.