Webhooks for Ash Framework — inbound (receive, verify, dedup → domain events) and outbound (sign, deliver, retry, track).

Attach to a resource:

use Ash.Resource,
  extensions: [AshHooks]

webhooks do
  # convention-resolves to AshHooks.Provider.ComplyCube
  inbound :comply_cube do
    secret {:app_env, [:my_app, :complycube_secret]}
    # optional: extract a stable event id (payload -> {:ok, id} | :error);
    # without it a deterministic content-hash identity is used
  end

  # convention-resolves to AshHooks.Provider.HubSpotV3 — the
  # vendor-default 300s replay window applies; override it either way
  inbound :hub_spot_v3 do
    secret {:app_env, [:my_app, :hubspot_client_secret]}
  end

  outbound :order_paid do
    signing_mode :dual
  end
end

Both halves are independently consumable: inbound-only consumers pull no queue infrastructure (ADR-0004).

webhooks

Webhook declarations — inbound sources and outbound events for this resource.

Nested DSLs

webhooks.inbound

inbound name

Declares an inbound webhook source — a provider whose deliveries this resource receives, verifies, deduplicates, and handles.

Arguments

NameTypeDefaultDocs
nameatomThe provider name (e.g. :comply_cube).

Options

NameTypeDefaultDocs
secretanyThe signing-secret source: an {M, f, a} callback, {:app_env, path}, or a function returning {:ok, secret} | {:error, :no_webhook_secret}. A literal binary is rejected at parse time (ADR-0005). Scope: this net catches the secret passed AS the option value; arguments of an MFA source are the consumer's own code.
provideratomThe provider MODULE implementing AshHooks.Provider. When unset, the ingress resolves AshHooks.Provider.<Camelized(name)> and fails closed when that module does not exist or does not implement the behaviour.
event_idanyExtractor for the provider's external event id from the decoded payload (payload -> {:ok, id} | :error). Providers without a stable id fall back to a deterministic content-hash identity — never a fresh UUID (ADR-0003).
replay_window_secondspos_integerReplay-protection window for providers whose scheme carries a trustworthy timestamp (e.g. HubSpot v3). Providers without timestamps (e.g. ComplyCube) MUST leave this unset — the verifier rejects a window whose provider declares no timestamp header (AshHooks.Provider.timestamp_header/1 returns nil). The window value is passed to the provider's verify_signature/3 in the context map for scheme-specific enforcement.

webhooks.outbound

outbound name

Declares an outbound webhook event this resource emits to subscribed endpoints — signed (Standard Webhooks canon by default) and delivered with retry/backoff/dead-letter semantics.

Arguments

NameTypeDefaultDocs
nameatomThe outbound event name (e.g. :order_paid).

Options

NameTypeDefaultDocs
signing_mode:legacy | :dual | :standard:standardSignature envelope: :standard (SW canon), :dual (SW canon + legacy envelope for migrating receivers), :legacy (ADR-0002).
subscriptionsatomThe consumer's Subscription resource module (carrying AshHooks.Subscription) this event fans out through — the dispatcher matches its rows against the event type.
deliveriesatomThe consumer's OutboundDelivery resource module (carrying AshHooks.OutboundDelivery) the dispatcher writes the durable per-endpoint rows into.