Mountable provider-aware Plug for streaming-provider webhooks.
Adopter wiring
Step 1 — install the body reader globally in endpoint.ex (BEFORE Plug.Parsers):
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
body_reader: {WebhookBodyReader, :read_body, []},
json_decoder: JasonStep 2 — mount the Plug in router.ex, one forward per provider:
forward "/webhooks/rindle/mux", Rindle.Delivery.WebhookPlug,
provider: Rindle.Streaming.Provider.Mux,
secrets: {:application, :rindle, [Rindle.Streaming.Provider.Mux, :webhook_secrets]}Step 3 — set RINDLE_MUX_WEBHOOK_SECRETS (comma-separated) in your runtime
config, and configure your Mux dashboard webhook to POST to
https://yourapp.example.com/webhooks/rindle/mux.
Secrets resolver shapes (D-02)
The :secrets option supports four shapes, resolved at every call/2
(NOT init/1) so runtime rotation works without a restart:
[binary()]— direct list (tests).{:system, env_var :: String.t()}— comma-split env var.{:application, app :: atom(), [atom()]}—Application.get_envgetter with optional path traversal into nested keyword lists / maps.(-> [binary()])— 0-arity function.
Response codes (D-12..D-16)
| Status | Body | When |
|---|---|---|
| 202 Accepted | empty | Verified + enqueued. |
| 200 OK | empty | Verified but dropped (event not in adapter dispatch table). |
| 400 Bad Request | provider_webhook_invalid | Signature mismatch, replay-window failure, missing secrets, callback raised. |
| 405 Method Not Allowed | method not allowed | Non-POST request. |
| 500 Internal Server Error | server_misconfigured | Body reader assign missing AND fallback empty. |
| 503 Service Unavailable | empty | Oban enqueue raised (transient downstream failure — Mux retries). |
Telemetry (Plug edge)
Events under [:rindle, :provider, :webhook, _]:
:verified— verify path returned{:ok, event}. Metadata:%{provider, event_type, event_id, kind}wherekind: :enqueued | :dropped.:rejected— verification or pre-verify check failed. Metadata:%{provider, reason, ...}wherereason: :method_not_allowed | :body_reader_missing | :no_secrets_configured | :provider_callback_raised | :sig_mismatch | :oban_unavailable.
Provider-internal telemetry under
[:rindle, :provider, :mux, :webhook_attempt, _] (emitted from inside
Rindle.Streaming.Provider.Mux.verify_webhook/3):
:secret_used— metadata%{secret_index}.:rejected— metadata%{secret_index, sdk_reason}.
Summary
Functions
init/1 validates the provider module exposes verify_webhook/3 and that
the secrets resolver shape is one of the four locked forms. Raises
ArgumentError for misconfigurations so deployment-time mistakes surface
immediately, not at first webhook delivery.
Functions
init/1 validates the provider module exposes verify_webhook/3 and that
the secrets resolver shape is one of the four locked forms. Raises
ArgumentError for misconfigurations so deployment-time mistakes surface
immediately, not at first webhook delivery.