> ## 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.

# Webhooks

> The three webhook events Flow payments sends, what each one carries, how delivery behaves, and how to reconcile a completed payment.

Flow payments sends a signed webhook to your server whenever one of a flow's three state axes changes. Webhooks are how your server learns that a payment completed, failed, or was blocked, without reading the flow repeatedly.

The events are available to every environment that subscribes to them. To start receiving them, follow [Setting up webhooks](https://docs.dynamic.xyz/docs/platform/console/webhooks/setup.md). [Webhook events](https://docs.dynamic.xyz/docs/platform/console/webhooks/event-types.md) lists the events of every Dynamic product.

## Events and axes

Each axis has its own event. An event carries the axis, the previous state, the new state, and data specific to the new state.

| Event | Axis | States |
| - | - | - |
| `flow.execution.updated` | Execution | `initiated`, `source_attached`, `quoted`, `signing`, `broadcasted`, `source_detected`, `source_confirmed`, `cancelled`, `expired`, `failed` |
| `flow.settlement.updated` | Settlement | `none`, `settling`, `completed`, `failed` |
| `flow.risk.updated` | Risk | `unknown`, `cleared`, `blocked` |

A payment is done when you receive `flow.settlement.updated` with `newState` set to `completed`. An execution event with `source_confirmed` means the payer's funds arrived, but delivery to your destination can still be in progress. [Payment lifecycle and states](https://docs.dynamic.xyz/docs/flow/lifecycle-walkthrough.md) describes every state.

## Event payload

Flow events share the envelope used by every Dynamic webhook, described in [Admin and environment events](https://docs.dynamic.xyz/docs/platform/console/webhooks/events.md). The flow-specific content is in `data`:

| Field | Meaning |
| - | - |
| `axis` | `execution`, `settlement`, or `risk` |
| `flowId` | The flow's ID |
| `previousState` | The state before the change. `none` on the first execution event of a flow |
| `newState` | The state after the change |
| `timestamp` | When the change happened |
| `additionalData` | Data specific to the new state, listed in [What each event carries](#what-each-event-carries) |

The payload describes the change, not the whole flow. To read the full flow, including `memo` and the delivered amount, use your server's API token to fetch it. The [API guide](https://docs.dynamic.xyz/docs/flow/api.md#read-a-flow-from-your-server) shows the request.

```json theme={"system"}
{
  "eventId": "2a92c161-3167-44ad-8fce-4c6cdaed8129",
  "messageId": "5a2a5360-bb7e-4ea6-9bd3-0146bf2f734f",
  "webhookId": "a86acea4-e050-4846-8e4f-0ae039f6e37c",
  "eventName": "flow.execution.updated",
  "environmentId": "123e4567-e89b-12d3-a456-426614174000",
  "environmentName": "live",
  "timestamp": "2026-06-15T12:00:00.000Z",
  "data": {
    "axis": "execution",
    "flowId": "7d2f1c8e-4b6a-4f0e-9a3d-5c1e2b8f6a90",
    "previousState": "initiated",
    "newState": "source_attached",
    "timestamp": "2026-06-15T12:00:00.000Z",
    "additionalData": {
      "fromAddress": "0x3FcE1F4F28DbA209344072867134A3a7F547C7f1",
      "fromChainId": "1",
      "sourceType": "wallet"
    }
  }
}
```

## What each event carries

### Execution

| New state | `additionalData` |
| - | - |
| `initiated` | None |
| `source_attached` | `sourceType`, and when known `fromAddress`, `fromChainId`, and `refundAddress` |
| `quoted` | `amount`, `currency`, `quoteVersion`, `toAddress`, `toChainId`, `toToken`, and when known `amountUsd` and `fromChainId` |
| `signing` | None |
| `broadcasted` | `txHash` when the wallet reported it |
| `source_detected` | `depositAddress` |
| `source_confirmed` | `txHash`, and when available `chainId`, `chainName`, `blockExplorerUrls`, and `exchangeTransactionId` |
| `cancelled` | None |
| `expired` | `depositAddress` |
| `failed` | `failure` |

The `source_confirmed` event identifies the payer's source transaction:

* **Wallet.** `txHash` is the transaction the payer's wallet sent.
* **Deposit address.** `txHash` is the deposit transaction as reported by the deposit provider. It is `null` when the provider does not report one.
* **Exchange.** The payer pays on the exchange, so there is no source transaction, and `txHash` is `null`. `exchangeTransactionId` is the exchange's ID for the payment.

When `txHash` and the source chain are known, `chainId` and `chainName` identify the chain the source transaction was sent on, and `blockExplorerUrls` links to it on that chain's explorer when one is configured. The full flow returned to your server also carries the source transaction as `txHash`.

### Settlement

| New state | `additionalData` |
| - | - |
| `settling` | `chainId` and `chainName` of the destination chain, when known |
| `completed` | `settlementTxHash`, `chainId`, `chainName`, `blockExplorerUrls`, `completedAt`, `settledAmount`, and `exchangeTransactionId` for exchange payments, each when available |
| `failed` | `failure`, and `chainId` and `chainName` when known |

In settlement events, `chainId`, `chainName`, and `blockExplorerUrls` describe the destination chain and the settlement transaction, not the payer's source transaction.

`settledAmount` is the amount that reached your destination:

| Field | Meaning |
| - | - |
| `amount` | The delivered amount, in the settlement token's smallest unit |
| `amountUsd` | The USD value of the delivered amount, when available |
| `tokenInfo` | The settlement token's `symbol` and `decimals`, and optionally its `name` and `logoURI`, when available |

`settledAmount` is present only when the provider or the chain reports the delivered amount.

```json theme={"system"}
{
  "eventId": "2a92c161-3167-44ad-8fce-4c6cdaed8129",
  "messageId": "5a2a5360-bb7e-4ea6-9bd3-0146bf2f734f",
  "webhookId": "a86acea4-e050-4846-8e4f-0ae039f6e37c",
  "eventName": "flow.settlement.updated",
  "environmentId": "123e4567-e89b-12d3-a456-426614174000",
  "environmentName": "live",
  "timestamp": "2026-06-15T12:00:00.000Z",
  "data": {
    "axis": "settlement",
    "flowId": "7d2f1c8e-4b6a-4f0e-9a3d-5c1e2b8f6a90",
    "previousState": "settling",
    "newState": "completed",
    "timestamp": "2026-06-15T12:00:00.000Z",
    "additionalData": {
      "chainName": "SOL",
      "chainId": "101",
      "blockExplorerUrls": [
        "https://explorer.solana.com/tx/<settlement-transaction-signature>"
      ],
      "completedAt": "2026-06-15T12:00:00.000Z",
      "settlementTxHash": "<settlement-transaction-signature>",
      "settledAmount": {
        "amount": "25000000",
        "amountUsd": "25",
        "tokenInfo": { "symbol": "USDC", "decimals": 6 }
      }
    }
  }
}
```

### Risk

Risk events carry no `additionalData`. They fire only when the risk state changes.

Fields with nothing to report are left out rather than sent as `null`, except `txHash` on `source_confirmed`.

## Delivery behavior

* **Same-state events.** An execution event can have the same `previousState` and `newState`, for example when the payer attaches a different source or requests a new quote. Treat it as an update, not as a new step.
* **Ordering.** Events can arrive in a different order from the one in which the changes happened. Use `newState` together with what you already recorded, and read the flow from your server when you need the current state.
* **Duplicates.** The same event can arrive more than once. Make your handler idempotent, as described in [Event delivery and best practices](https://docs.dynamic.xyz/docs/platform/console/webhooks/delivery-best-practices.md).
* **Signatures.** Verify each event's signature before acting on it.

Webhooks are the right default for a production server. For payments, also consider a scheduled job on your server that reads the flows you are still waiting on, so that a missed event does not leave an order unfulfilled.

## Reconciling a completed payment

When `flow.settlement.updated` arrives with `newState` set to `completed`:

1. Match the event to your order by `flowId`. Fetch the flow from your server to read its `memo` or any other reference you stored on it, because webhook payloads do not include `memo`.
2. Record the settlement transaction with `settlementTxHash`, `chainId`, and `chainName` when they are present, and use `blockExplorerUrls` to link to it.
3. For the source side of a receipt, use `txHash` from the `source_confirmed` event or from the flow. For an exchange payment, which has no source transaction, use `exchangeTransactionId`.
4. In `deposit` mode, credit `settledAmount`, not the flow's requested amount, because a deposit can settle for less than requested. When `settledAmount` is absent, verify the amount on chain from the settlement transaction before crediting it. In `payment` mode, `completed` means the payment was accepted in full. See [Short payments depend on the mode](https://docs.dynamic.xyz/docs/flow/lifecycle-walkthrough.md#short-payments-depend-on-the-mode).

## Next

> [card] [Webhook setup](https://docs.dynamic.xyz/docs/platform/console/webhooks/setup.md)
>
> Configuring endpoints and verifying signatures.

> [card] [Failures and refunds](https://docs.dynamic.xyz/docs/flow/failures-and-refunds.md)
>
> The failure record that `failed` events carry.


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