> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Business account proposal webhooks

> Events emitted as an approval proposal is created, approved, withdrawn, vetoed, executed, or expired.

Proposal events describe an approval workflow rather than a change to the account, so they are separate from the [account action events](https://docs.dynamic.xyz/docs/business-accounts/webhooks/account-actions.md). They fire under the `businessAccount.proposal.*` namespace.

For the shared event envelope, endpoint setup, retries, and signature verification, see [Setting up webhooks](https://docs.dynamic.xyz/docs/platform/console/webhooks/setup.md) and [Event delivery & best practices](https://docs.dynamic.xyz/docs/platform/console/webhooks/delivery-best-practices.md). The current list of event types is always available from the [event types endpoint](https://docs.dynamic.xyz/docs/api-reference/events/get-event-types.md).

## Events

An account can require approvals before a change applies. A change that needs them does not take effect immediately: a **proposal** opens, collects approvals, and applies once its requirements are met. These six events cover that lifecycle.

> [!NOTE]
>
> An approved change sends **two** events: the proposal event below, and the ordinary account event for the change itself. A governed member removal sends both `businessAccount.proposal.executed` and `businessAccount.member.removed`. Subscribe to the family you need: the proposal events for approval workflow, the account events for keeping your own records in step.

**businessAccount.proposal.created** `object`

Occurs whenever a change needs approvals and a proposal opens. Carries who to ask, and how many people are needed.

**businessAccount.proposal.approved** `object`

Occurs whenever an approval is recorded. Carries the approver and the time they signed.

**businessAccount.proposal.approvalWithdrawn** `object`

Occurs when an approval stops counting: either the approver withdrew it,
or it was revoked because their standing changed (a role change, an
ownership transfer that demotes them, or a removal). Approval counts can
decrease, and a membership change emits one event per affected proposal.

**businessAccount.proposal.vetoed** `object`

Occurs whenever a proposal is refused. A veto is final, whatever approvals
the proposal already holds. Removing a member, demoting them through an
ownership transfer, or changing their role vetoes every open proposal they
initiated, so one membership change can emit several vetoed events.

**businessAccount.proposal.executed** `object`

Occurs whenever a proposal meets its requirements and the change applies.

**businessAccount.proposal.expired** `object`

Occurs when a proposal that passed its `completeBy` deadline without executing
or being vetoed is marked expired. Carries `expiredAt`, the deadline the
proposal missed. The event has no actor: the clock, not a member, closed the
proposal. A proposal can still read `pending` after its deadline until it is
marked expired, so compare `completeBy` with the current time as well as
reading `status`.

### Reading a proposal payload

`evaluationSummary` answers whether the change can go through. `ruleEvaluations` answers why, one entry per governance rule that applies.

> [!WARNING]
>
> Do not add up the `requiredCount` values in `ruleEvaluations`. One approval counts toward every rule its approver is eligible for, so the total overstates what is needed. Check `evaluationSummary.isSatisfied` for completion, and `evaluationSummary.minimumAdditionalApprovals` for how many more people the change needs.

```json theme={"system"}
{
  "proposalId": "prp_9f3c",
  "businessAccountId": "ba_acme",
  "action": "removeMember",
  "initiatorUserId": "usr_lee",
  "targetUserId": "usr_dana",
  "completeBy": "2026-10-14T18:22:40.881Z",
  "changeSet": [{ "type": "removeMember", "userId": "usr_dana" }],
  "evaluationSummary": {
    "isSatisfied": false,
    "status": "pendingApprovals",
    "minimumAdditionalApprovals": 2
  },
  "currentApprovals": [],
  "pendingEligibleApprovers": ["usr_ray", "usr_kim", "usr_max", "usr_nia"],
  "mandatoryApprovers": ["usr_ray"],
  "ruleEvaluations": [
    {
      "ruleId": "removeMember:offboard-dual-control",
      "governedAction": "removeMember",
      "isSatisfied": false,
      "requiredCount": 2,
      "contributingApprovers": [],
      "remainingEligiblePool": ["usr_ray", "usr_kim", "usr_max", "usr_nia"]
    },
    {
      "ruleId": "removeSignerFromWallet:share-revocation:wlt_treasury",
      "governedAction": "removeSignerFromWallet",
      "isSatisfied": false,
      "requiredCount": 1,
      "contributingApprovers": [],
      "remainingEligiblePool": ["usr_ray", "usr_kim", "usr_max"]
    }
  ]
}
```

Four fields repay a closer look:

* **`changeSet`** holds only what the person asked for. Removing a member also revokes their key shares, and that extra rule shows up as its own entry in `ruleEvaluations` with `governedAction: "removeSignerFromWallet"`, not as a second entry in `changeSet`.
* **`ruleId`** combines the rule's own name from the account's governance configuration with the action it governs, plus a wallet id when the rule targets one. A rule's name is unique only within one action, and a member's key shares span every wallet they sign on, so a rule revoking those shares produces one `ruleEvaluations` entry per wallet. Both parts of `ruleId` keep those entries apart.
* **`remainingEligiblePool`** can differ between rules. A rule limited to people who hold a key share on a particular wallet admits fewer people than a rule that does not. Send each person the rule they can act on.
* **`mandatoryApprovers`** names people, not roles: someone appears here when every way of satisfying the account's rules needs their approval specifically. `pendingEligibleApprovers` is the union of everyone still worth asking across every unsatisfied rule, and `mandatoryApprovers` is the ones among them who cannot be skipped.

> [!NOTE]
>
> Once a proposal reaches `vetoed`, `expired`, or `executed`, `ruleEvaluations`, `pendingEligibleApprovers`, and `mandatoryApprovers` all report empty. `currentApprovals` still holds the full record of who approved. `evaluationSummary.isSatisfied` reflects the final outcome (`true` only for `executed`) rather than the tally at the moment the account closed the proposal.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.