# `AshHooks`
[🔗](https://github.com/baselabs/ash_hooks/blob/v2.0.3/lib/ash_hooks.ex#L1)

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);
        # otherwise the provider identity callback, then raw-body digest, applies
      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).

Multi-tenant? Declare Ash attribute multitenancy on the four resources
(subscription, endpoint, both ledgers) and pass `:tenant` /
`ctx[:tenant]` to every entry point — every query, write, dedup
identity, sweep, and job arg is tenant-scoped, and a tenant-less call
against a multitenant resource fails closed with
`{:error, :tenant_required}` before any data access. Single-tenant
apps change nothing. The
[adoption checklist](https://github.com/baselabs/ash_hooks/blob/main/documentation/tutorials/tenancy-adoption-checklist.md)
walks the ordered transition (ADR-0011).

# `sweep_totals`

```elixir
@type sweep_totals() :: %{
  results: %{optional(term()) =&gt; {:ok, non_neg_integer()} | {:error, term()}},
  total: non_neg_integer()
}
```

The per-tenant sweep totals: one result per tenant (error-isolated — one
tenant's failure never stops the others) plus the sum of the successful
counts.

# `dispatch`

```elixir
@spec dispatch(module(), atom(), AshHooks.Event.t() | term(), keyword()) ::
  {:ok, [AshHooks.Dispatcher.dispatch_result()]} | {:error, term()}
```

Fans an outbound event out to its subscribed endpoints — the outbound
entry point, delegating to `AshHooks.Dispatcher.dispatch/4` (the
`AshHooks.Ingress.ingest/4` twin).

# `prune_all`

```elixir
@spec prune_all(module(), keyword()) :: {:ok, sweep_totals()} | {:error, term()}
```

Maps the retention prune over `tenants:` sequentially (routing by the
resource's extension: inbound ledgers through `AshHooks.Ingress.prune/2`,
outbound deliveries through `AshHooks.Delivery.prune/2`) — the same
per-tenant isolation and sequencing as `reap_all/2`.

    {:ok, %{results: %{"org_a" => {:ok, 2}}, total: 2}} =
      AshHooks.prune_all(Delivery, older_than: cutoff, tenants: ["org_a"])

# `reap_all`

```elixir
@spec reap_all(module(), keyword()) :: {:ok, sweep_totals()}
```

Maps `AshHooks.Ingress.reap/2` over `tenants:` SEQUENTIALLY, per-tenant
error isolation — a tenant-less global reap over a multitenant ledger is
impossible by construction (each tenant's call carries its own
pre-flight). Sequential by design: the shared repo pool is the one
cross-tenant starvation surface, so per-tenant sweeps do not contend.

    {:ok, %{results: %{"org_a" => {:ok, 3}, "org_b" => {:ok, 0}}, total: 3}} =
      AshHooks.reap_all(Ledger, tenants: ["org_a", "org_b"])

# `reconcile_pending`

```elixir
@spec reconcile_pending(module(), atom(), keyword()) ::
  {:ok, [AshHooks.Dispatcher.dispatch_result()]} | {:error, term()}
```

Recovers eligible outbound rows for a declaration and tenant, including
interrupted enqueue, due retries, and expired send leases. Schedule this
periodically for each outbound declaration and tenant; it preserves retry
timing and attempt counts. See `AshHooks.Dispatcher.reconcile_pending/3`.

# `validate_secret_source`

```elixir
@spec validate_secret_source(term()) :: {:ok, term()} | {:error, String.t()}
```

Schema validator for the `secret` option — accepts only secret SOURCES,
rejecting a literal binary at DSL parse time (ADR-0005).

# `webhooks`
*macro* 

---

*Consult [api-reference.md](api-reference.md) for complete listing*
