Skip to main content
Business Accounts are in early access. Transaction quorum currently covers EVM. Solana and Bitcoin transaction support is coming soon. See Approvals for signing.

Introduction

This cookbook covers a treasury wallet on a business account that pays an allowlisted vendor. The payment has to pass three policy rules before the wallet can sign:
  • The account allows only that vendor’s address.
  • The wallet caps how much one transaction can send.
  • At or above the amount named in a quorum policy, two admins have to approve. The member who starts the payment does not count as one of them.
Your goal is to build a system to track every stage the transaction goes through, surface any issues, and ensure any approvers see that they need to do so. We will start by identifying the right events to track, then create a webhook for them, record each step and build an approver inbox. Along the way we will also cover some best practices around event recovery and security.

Event types

Here are the events the payment moves through, in order. What each event tells you in context comes up in the sections below. The full payload shapes are in Policy violation webhooks, Business account proposal webhooks, and Transaction events.

Subscribe to the payment

One webhook covers the whole flow. Subscribe it to every event in the table.
Open Developer > Webhooks, create a webhook pointing at your HTTPS endpoint, and select the businessAccount.proposal.* events, waas.policy.violation, and the wallet.transaction.* events.
Dynamic sends a ping test payload when the webhook registers. See Setting up webhooks for the full procedure.

Record each step

Before trusting a message, check two things. The signature: x-dynamic-signature-256 should be the HMAC-SHA256 of the raw body, computed with the webhook’s secret. And whether you have seen it before: messages arrive at least once, so keep a record of each messageId you process and drop the repeats. Both mechanics are covered in Setting up webhooks and Event delivery & best practices.
The notify and mark calls stand in for your own code. Map the Dynamic user IDs to your members, and send the notification over whatever channel they watch: email, Slack, or your own push. Three payload details matter while the approvals are still open:
  • evaluationSummary.isSatisfied tells you whether the wallet can sign; minimumAdditionalApprovals tells you how many approvals are still missing. Do not add up requiredCount across ruleEvaluations: a single approval counts toward every rule its approver is eligible for. See Reading a proposal payload for the full field list.
  • Once a proposal reaches vetoed, expired, or executed, pendingEligibleApprovers, mandatoryApprovers, and ruleEvaluations report empty. Capture your routing targets on created and approved, not at close.
  • businessAccount.proposal.expired fires shortly after the completeBy deadline, not at the deadline itself, and carries expiredAt instead of an actor.
Events can also arrive out of order, so trust the timestamp and the final status over what came in when: an executed you never saw an approved for is still the signature; a completed you never saw a broadcasting for is still the landing.

Confirm the payment landed

businessAccount.proposal.executed means the approvals are in and the wallet signed. What you hold at that point is a signed transaction that has not reached the chain yet. How it gets there decides which events you will see:
  • Dynamic broadcasts it on the gas-sponsored EVM path. Then wallet.transaction.completed means it landed, reverted means the chain saw it but the contract refused it, and failed means it never arrived. broadcasting and confirming can be skipped, so only those three count.
  • Your application broadcasts it. Then none of the wallet.transaction.* events fire at all, and your own chain watcher is what confirms the landing.
Either way, the landing event does not carry the proposal id, so keep your own link from proposalId to the transaction.

Reconcile missed events

Messages get missed: an endpoint outage, a disabled webhook, a deploy at the wrong moment. The events feed is the same stream, pulled instead of pushed, so poll it on a schedule or after re-enabling a webhook to replay what was missed. resourceType picks which events you want by matching the start of the event name: businessAccount.proposal for the approvals, waas.policy for a block, wallet.transaction for a payment Dynamic broadcast.
Reading the feed needs an API token with events read permission; see API token permissions for assigning scopes. A few limits apply:
  • startDate can look back at most 30 days, and events are kept for 30 days in sandbox and 90 days in live.
  • Results are unordered, so sort by timestamp yourself.
  • The same eventId can show up in both the feed and a delivered webhook, so dedupe both against the same seen set.
To replay one specific delivery instead, use POST /environments/{environmentId}/webhooks/{webhookId}/messages/{messageId}/redeliver.

Show an admin their approval queue

The webhook tells you the payment is waiting. The admin’s own inbox should use the SDK, which returns only the proposals that member can act on. serializedTransaction is the unsigned payment your application prepared, and walletAccount is the treasury wallet that will sign it.
A BusinessAccountProposal tells you its status (pending, executed, vetoed, or expired), its completeBy deadline, approvalRequirements, and intent. To see whether it can still be satisfied, read approvalRequirements[].satisfied rather than counting who has already approved: that approvals list includes people whose approval no longer counts. Resuming the signature is covered in Quorum policies.

Operational notes

  • Your endpoint must respond within 15 seconds or the delivery counts as failed and is retried. Accept messages fast and process them asynchronously.
  • If deliveries keep failing, Dynamic switches the webhook off: 200 failures in a row, or 250 in sandbox and 1500 in live within 30 days. Turning it back on is manual, so alert on that state and reconcile through the events feed once it is back.
  • An environment supports 10 webhooks, and messages are retained for 30 days in sandbox and 90 days in live. The full delivery contract is in Event delivery & best practices.
Last modified on October 1, 2026