# Create a Payout (from a Wallet)

This guide is for payout creation when funds are held in self-custodial wallets.

Before creating a payout, review the [Funds Movement Lifecycle and Data Model](/overviews/funds-movement-lifecycle-and-data-model) overview to understand the shared data shape, lifecycle phases, and statuses that apply to payouts and Tesser's other funds-movement resources.

## Successful Payout API and Webhook Sequence

The diagrams below show the end-to-end sequence for a successful wallet payout: solid arrows are API calls you make; dashed arrows are synchronous responses, blockchain/provider responses, and webhook events. Webhook arrows are labelled `type=<event>` followed by any fields the event changes, then a plain-language annotation. Step numbering follows the create-then-PATCH variant (direct-create flows skip the PATCH steps), and the failure-path diagrams later on this page continue the numbering of the flow they belong to, from the step where it diverges. In multi-step payouts the strict ordering shown is illustrative — a later step may begin before an earlier one fully finalizes (see [Execution](/overviews/funds-movement-lifecycle-and-data-model#execution)); follow the emitted `step.*` events for actual sequencing.

<Tabs>
  <TabItem label="Stablecoin">

<Mermaid
  chart={`
sequenceDiagram
    autonumber
    participant You as Your integration
    participant Signer as LocalSigner SDK
    participant Tesser
    participant Chain as Blockchain network
    Note over You,Tesser: Get a quote for the payout
    You->>Tesser: POST /v1/payments — desired currencies, amount, networks
    Tesser-->>You: 200 — payment id, desired.* echoed
    Tesser-->>You: webhook: type=payment.quote_created — estimated.* populated,<br/>steps[] partially populated
    opt
        Note over You,Tesser: Two-step flow: Update the payment with account ids if omitted at creation
        You->>Tesser: PATCH /v1/payments/{id} — funding_account_id, desired.from/to.account_id
        Tesser-->>You: webhook: type=payment.updated — all desired.* fields echoed,<br/>steps[] fully populated
    end
    Note over Tesser: Screens beneficiary wallet against your risk policy
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=automatically_approved
    Tesser-->>You: webhook: type=step.signature_requested — unsigned transaction payload
    Note over You: Sign before expires_at. Wallet address can be obtained<br/>from your database or cache, or by calling<br/>GET /v1/accounts/{estimated.from.account_id}
    You->>Signer: signStep — unsigned_transaction, wallet address, network
    Signer-->>You: signature
    You->>Tesser: POST /v1/payments/{id}/steps/{stepId}/sign
    Note over Tesser: desired.from.account_id balance checked synchronously<br/>as part of signing execution
    Tesser-->>You: 200 — signature accepted
    Tesser-->>You: webhook: type=payment.balance_updated, balance_status=reserved
    Tesser-->>You: webhook: type=step.signed
    Tesser->>Chain: Broadcast signed transaction — gas sponsored by Tesser
    Tesser-->>You: webhook: type=step.submitted — indicates timestamp<br/>when Tesser broadcasted transaction on chain
    Chain-->>Tesser: Accepted into mempool
    Tesser-->>You: webhook: type=step.confirmed — transaction_hash and fees[] populated
    Chain-->>Tesser: Transaction reaches finality on-chain
    Tesser-->>You: webhook: type=step.completed — step actual.* fields populated
    Tesser-->>You: webhook: type=payment.updated — terminal, top-level actual.* fields populated
`}
/>

  </TabItem>
  <TabItem label="Fiat">

<Mermaid
  chart={`
sequenceDiagram
    autonumber
    participant You as Your integration
    participant Tesser
    participant Provider as Fiat off-ramp provider
    Note over You,Tesser: Get an FX quote for the payout
    You->>Tesser: POST /v1/payments — from USDC on ETHEREUM, to MXN, to.network null
    Tesser-->>You: 200 — payment id, desired.* echoed
    Tesser-->>You: webhook: type=payment.quote_created — two steps planned
    Note over Tesser: estimated.from 55.85 USDC to estimated.to 1000 MXN<br/>(ratio is the indicative FX rate)
    opt
        Note over You,Tesser: Two-step flow: Update the payment with account ids if omitted at creation
        You->>Tesser: PATCH /v1/payments/{id} — funding_account_id, desired.from/to.account_id
        Tesser-->>You: webhook: type=payment.updated — complete desired.*, re-planned steps[]
    end
    Tesser->>Provider: Request the deposit address for the off-ramp transaction
    Provider-->>Tesser: Respond with on-chain deposit wallet address<br/>(where to send the USDC)
    Note over Tesser: Screens that provider deposit address against your risk policy
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=automatically_approved
    Note over You,Provider: Step 1 — the on-chain transfer to that provider deposit address<br/>runs exactly as the Stablecoin flow above, through step.completed for step 1
    rect rgba(127,127,127,0.07)
        Note over You,Provider: Step 2 — provider delivers fiat to the beneficiary
        Tesser-->>You: webhook: type=step.submitted
        Provider-->>Tesser: Confirm on-chain deposit received
        Tesser-->>You: webhook: type=step.confirmed
        Provider-->>Tesser: Confirmation that payout has completed to beneficiary
        Tesser-->>You: webhook: type=step.completed — actual.* fields populated
    end
    Tesser-->>You: webhook: type=payment.updated — terminal, top-level actual.* fields populated
`}
/>

  </TabItem>
</Tabs>

## Payout Creation
To create a payout, send a POST request to the [Payments API](/api/payments#create-payment).

At a minimum, supply the following information to create the payout record in the `desired` object:

- **Currencies**: `desired.from.currency` and `desired.to.currency`.
- **Amounts**: Either `desired.from.amount` or `desired.to.amount` (not both).
- **Networks**: For stablecoin payouts, specify both `desired.from.network` and `desired.to.network`. For fiat payouts, the receiving network is `null`.

You may also supply `desired.from.account_id`, `desired.to.account_id`, and `funding_account_id` at this stage, or omit them and PATCH them later — see [Update the payout with account information](#update-the-payout-with-account-information).

<Tabs>
  <TabItem label="Stablecoin">

Example request for stablecoin payout creation:

```json
{
  "desired": {
    "from": {
      "amount": "1000",
      "currency": "USDC",
      "network": "ETHEREUM"
    },
    "to": {
      "currency": "USDC",
      "network": "ETHEREUM"
    }
  },
  "organization_reference_id": "ref_123"
}
```

  </TabItem>
  <TabItem label="Fiat">

Example request for fiat payout creation:

```json
{
  "desired": {
    "from": {
      "currency": "USDC",
      "network": "ETHEREUM"
    },
    "to": {
      "amount": "1000",
      "currency": "MXN",
      "network": null
    }
  },
  "organization_reference_id": "ref_123"
}
```

  </TabItem>
</Tabs>

:::note
Only one of `desired.from.amount` or `desired.to.amount` may be specified in the request, never both.
:::

In the synchronous response, you receive an `id` for the payout that you can use for subsequent PATCH or GET calls or to track webhook updates received for this payout. The `desired.*` overlay echoes what you submitted; `estimated.*` and `actual.*` are populated as the payout progresses.

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example response for stablecoin payout creation</summary>

```json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440020",
    "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
    "organization_reference_id": "ref_123",
    "direction": "outbound",
    "funding_account_id": null,
    "desired": {
      "from": {
        "account_id": null,
        "amount": "1000",
        "currency": "USDC",
        "network": "ETHEREUM"
      },
      "to": {
        "account_id": null,
        "amount": null,
        "currency": "USDC",
        "network": "ETHEREUM"
      }
    },
    "estimated": {
      "from": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      },
      "to": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      }
    },
    "actual": {
      "from": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      },
      "to": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      }
    },
    "risk_status": "unchecked",
    "risk_status_reasons": [],
    "participants": [],
    "provider_metadata": [],
    "risk_reviewed_by": null,
    "risk_reviewed_at": null,
    "balance_status": "unreserved",
    "balance_reserved_at": null,
    "steps": [],
    "created_at": "2025-12-01T09:00:00.000Z",
    "updated_at": "2025-12-01T09:00:00.000Z",
    "expires_at": "2025-12-01T23:59:59.999Z"
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example response for fiat payout creation</summary>

```json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440020",
    "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
    "organization_reference_id": "ref_123",
    "direction": "outbound",
    "funding_account_id": null,
    "desired": {
      "from": {
        "account_id": null,
        "amount": null,
        "currency": "USDC",
        "network": "ETHEREUM"
      },
      "to": {
        "account_id": null,
        "amount": "1000",
        "currency": "MXN",
        "network": null
      }
    },
    "estimated": {
      "from": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      },
      "to": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      }
    },
    "actual": {
      "from": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      },
      "to": {
        "account_id": null,
        "amount": null,
        "currency": null,
        "network": null
      }
    },
    "risk_status": "unchecked",
    "risk_status_reasons": [],
    "participants": [],
    "provider_metadata": [],
    "risk_reviewed_by": null,
    "risk_reviewed_at": null,
    "balance_status": "unreserved",
    "balance_reserved_at": null,
    "steps": [],
    "created_at": "2025-12-01T09:00:00.000Z",
    "updated_at": "2025-12-01T09:00:00.000Z",
    "expires_at": "2025-12-01T23:59:59.999Z"
  }
}
```

</details>

  </TabItem>
</Tabs>

## Payout Quote Created (`payment.quote_created`)

After you create the payout, Tesser asynchronously plans the route — sourcing the best exchange rate (if cross-currency) and computing the sequence of `steps[]` needed to deliver funds. Both pieces are emitted on a single `payment.quote_created` webhook. See [Planning](/overviews/funds-movement-lifecycle-and-data-model#planning) on the lifecycle overview for the full picture.

The webhook populates:
- The top-level `estimated.*` overlay. For same-currency moves, `estimated.*` matches `desired.*` 1:1. For cross-currency moves, the ratio of `estimated.from.amount` to `estimated.to.amount` is the indicative exchange rate.
- The `steps[]` array. Each step starts with `status: "created"` and its own `estimated.{from,to}` overlay; `actual.{from,to}` is null until the step executes.

:::note
If you created the payout without `desired.*.account_id` fields, those fields stay `null` on the `payment.quote_created` payload too. Customer-side `account_id` fields on the steps — `estimated.from.account_id` on step 1 (the source wallet) and `estimated.to.account_id` on the last step (the final beneficiary) — also stay `null` until you PATCH accounts and Tesser updates the plan (see next section). Provider-ledger account ids on intermediate step boundaries may already be populated.
:::

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example `payment.quote_created` webhook for a stablecoin payout (same-currency, single on-chain step)</summary>

```json
{
  "id": "8472fb87-73b3-45ee-8020-a3496b4fc7a1",
  "type": "payment.quote_created",
  "created_at": "2025-12-01T09:00:00.045Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": null,
      "desired": {
        "from": {
          "account_id": null,
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": null,
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": null,
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "unchecked",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": null,
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": null,
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.040Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:00.040Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example `payment.quote_created` webhook for a fiat payout (cross-currency, two steps)</summary>

```json
{
  "id": "8472fb87-73b3-45ee-8020-a3496b4fc7a1",
  "type": "payment.quote_created",
  "created_at": "2025-12-01T09:00:00.045Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": null,
      "desired": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": null,
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "estimated": {
        "from": {
          "account_id": null,
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": null,
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "unchecked",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": null,
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.040Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        },
        {
          "id": "a7b3d912-5f8e-4c23-9d6a-1e4f7b2c8a5d",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 2,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "provider",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.040Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:00.040Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

## Update the Payout with Account Information

If you did not supply account information at creation, submit a PATCH request to the [Payments API](/api/payments#update-payment) to populate:

- `desired.from.account_id`: Wallet on the Tesser platform that funds will come from. Because this guide is about wallet payouts, ensure this is a managed wallet (`is_managed = true`).
- `desired.to.account_id`: Wallet or bank account of the beneficiary that funds will be delivered to.
- `funding_account_id`: Fiat bank account of the ultimate originator of the payout.

:::note
Because the Tesser platform serves financial institutions, the `desired.from.account_id` may belong to a counterparty, tenant, or the workspace. `funding_account_id` is therefore required to determine the ultimate originating entity of the payout to fulfill Travel Rule obligations. See [Travel Rule](/overviews/compliance-and-risk#travel-rule).
:::

<Tabs>
  <TabItem label="Stablecoin">

Example PATCH request for stablecoin payout updated with accounts:

```json
{
  "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
  "desired": {
    "from": {
      "account_id": "550e8400-e29b-41d4-a716-446655440011"
    },
    "to": {
      "account_id": "550e8400-e29b-41d4-a716-446655440012"
    }
  }
}
```

  </TabItem>
  <TabItem label="Fiat">

Example PATCH request for fiat payout updated with accounts:

```json
{
  "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
  "desired": {
    "from": {
      "account_id": "550e8400-e29b-41d4-a716-446655440011"
    },
    "to": {
      "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047"
    }
  }
}
```

  </TabItem>
</Tabs>

After the PATCH succeeds, Tesser publishes a `payment.updated` webhook reflecting the now-complete `desired.*` overlay. This is unique to payouts — deposits, withdrawals, and rebalances require all `desired.*` fields at creation, so they don't get this mid-lifecycle event. (A second `payment.updated` fires later when the payout reaches its terminal state — see [Payout terminal state](#payout-terminal-state).)

Tesser will also update the route plan now that account ids are known: step-level `account_id` fields are populated and `provider_key` may be set on each step. If `payment.quote_created` already fired during creation with `null` account_ids, Tesser's `payment.updated` after PATCH carries the updated `steps[]`.

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example `payment.updated` webhook after PATCHing accounts on a stablecoin payout</summary>

```json
{
  "id": "1c9a4f6e-8b3d-4f23-a056-9d7c4b2e1f08",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:00.205Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "unchecked",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.200Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:00.200Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example `payment.updated` webhook after PATCHing accounts on a fiat payout</summary>

```json
{
  "id": "1c9a4f6e-8b3d-4f23-a056-9d7c4b2e1f08",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:00.205Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "unchecked",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.200Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        },
        {
          "id": "a7b3d912-5f8e-4c23-9d6a-1e4f7b2c8a5d",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 2,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "provider",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.200Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:00.200Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

## Payout Risk Review (`payment.risk_updated`)

Once you supply `desired.to.account_id`, Tesser screens the beneficiary's wallet (and any intermediate wallets in the route) against your organization's risk policy. The outcome is reported on the payment as `risk_status` via a `payment.risk_updated` webhook. `risk_status_reasons` is populated with the reason codes when the outcome is `awaiting_decision`, `automatically_rejected`, or `manually_rejected`; it stays empty on approvals. See [Risk Statuses](/overviews/funds-movement-lifecycle-and-data-model#risk-statuses) on the lifecycle overview for the full taxonomy.

If your policy requires manual review, submit a decision via the [risk review decision API](/api/payments#submit-risk-review) or in the Tesser dashboard. After a decision is recorded, `risk_reviewed_by` and `risk_reviewed_at` are populated and `risk_status` transitions to `manually_approved` or `manually_rejected`.

<Tabs>
  <TabItem label="Automatically approved">

<Mermaid
  chart={`
sequenceDiagram
    autonumber 6
    participant You as Your integration
    participant Tesser
    Note over You,Tesser: Payout created and account ids supplied (at POST or via PATCH)
    Note over Tesser: Screens beneficiary + intermediate wallets<br/>against your risk policy
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=automatically_approved
    Note over You,Tesser: Flow continues — step.signature_requested follows
`}
/>

  </TabItem>
  <TabItem label="Manual review: approved">

<Mermaid
  chart={`
sequenceDiagram
    autonumber 6
    participant You as Your integration
    participant Tesser
    Note over You,Tesser: Payout created and account ids supplied (at POST or via PATCH)
    Note over Tesser: Screens beneficiary + intermediate wallets<br/>against your risk policy
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=awaiting_decision —<br/>risk_status_reasons[] populated
    You->>Tesser: Submit decision — risk review via API or Tesser dashboard
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=manually_approved —<br/>risk_reviewed_by and risk_reviewed_at set
    Note over You,Tesser: Flow continues — step.signature_requested follows
`}
/>

  </TabItem>
  <TabItem label="Manual review: rejected">

<Mermaid
  chart={`
sequenceDiagram
    autonumber 6
    participant You as Your integration
    participant Tesser
    Note over You,Tesser: Payout created and account ids supplied (at POST or via PATCH)
    Note over Tesser: Screens beneficiary + intermediate wallets<br/>against your risk policy
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=awaiting_decision —<br/>risk_status_reasons[] populated
    You->>Tesser: Submit decision — risk review via API or Tesser dashboard
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=manually_rejected
    Tesser-->>You: webhook: type=step.failed — each step goes to failed,<br/>step actual.* fields null
    Tesser-->>You: webhook: type=payment.updated — terminal,<br/>top-level actual.* fields all null
    Note over You: Terminal — resolve the risk factors and create a new payout
`}
/>

  </TabItem>
  <TabItem label="Automatically rejected">

<Mermaid
  chart={`
sequenceDiagram
    autonumber 6
    participant You as Your integration
    participant Tesser
    Note over You,Tesser: Payout created and account ids supplied (at POST or via PATCH)
    Note over Tesser: Screens beneficiary + intermediate wallets<br/>against your risk policy
    Tesser-->>You: webhook: type=payment.risk_updated, risk_status=automatically_rejected —<br/>risk_status_reasons[] populated
    Tesser-->>You: webhook: type=step.failed — each step goes to failed,<br/>step actual.* fields null
    Tesser-->>You: webhook: type=payment.updated — terminal,<br/>top-level actual.* fields all null
    Note over You: Terminal — resolve the risk factors and create a new payout
`}
/>

  </TabItem>
</Tabs>

## Payout Step Signing (`step.signature_requested`)

Once a payout has been auto-approved or manually approved via risk review, Tesser will send a webhook with event type `step.signature_requested` with the necessary information to sign the on-chain payment step.

:::warning{title="Payouts expire"}
Ensure you proceed with step signing prior to the expiration time, as indicated by the payout's `expires_at` timestamp. If a payout expires, it cannot be resurrected. To retry, create a new payout. See [Expiration](/overviews/funds-movement-lifecycle-and-data-model#expiration) for details.
:::

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example webhook schema for stablecoin payout requesting signature</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "step.signature_requested",
  "created_at": "2025-12-01T09:00:00.802Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": null,
      "unsigned_transaction": "0x02ed81893a85 ... 3a764000080c0",
      "fees": [],
      "provider_key": "turnkey",
      "status": "signature_requested",
      "status_reasons": [],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:00.800Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": null,
      "submitted_at": null,
      "confirmed_at": null,
      "completed_at": null,
      "failed_at": null
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example webhook schema for fiat payout requesting signature</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "step.signature_requested",
  "created_at": "2025-12-01T09:00:00.802Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": null,
      "unsigned_transaction": "0x02ed81893a85 ... 3a764000080c0",
      "fees": [],
      "provider_key": "turnkey",
      "status": "signature_requested",
      "status_reasons": [],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:00.800Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": null,
      "submitted_at": null,
      "confirmed_at": null,
      "completed_at": null,
      "failed_at": null
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

To sign the step, build a `StepForSigning` from the webhook step and pass it to `LocalSigner.signStep`. It needs three values: the `unsigned_transaction`, the source wallet's on-chain address (`crypto_wallet_address` from `GET /v1/accounts/{estimated.from.account_id}`), and the network. Submit the returned `signature` to the [Sign payment step API](/api/payments#sign-payment-step) to execute the payment. See [Sign a Wallet Step](/how-tos/sign-a-wallet-step#sign-the-step-with-the-localsigner-sdk) for install and the full SDK reference.

<Tabs>
  <TabItem label="TypeScript">

```typescript
import { LocalSigner, type StepForSigning } from "@tesser-payments/sdk-ts";

const signer = new LocalSigner({
  signing: {
    publicKey: process.env.SIGNING_PUBLIC_KEY!,
    privateKey: process.env.SIGNING_PRIVATE_KEY!,
    enclaveId: process.env.SIGNING_ENCLAVE_ID!,
  },
});

// 1. Extract the step from a step.signature_requested webhook event.
const step = webhookPayload.data.object;

// 2. Resolve the source wallet's on-chain address.
const account = await fetch(
  `https://api.tesser.xyz/v1/accounts/${step.estimated.from.account_id}`,
  { headers: { Authorization: `Bearer ${token}` } }
).then((res) => res.json());

// 3. Sign locally.
const toSign: StepForSigning = {
  unsignedTransaction: step.unsigned_transaction,
  signWith: account.crypto_wallet_address,
  network: step.estimated.from.network,
};
const { signature } = await signer.signStep(toSign);

// 4. Submit the signature to execute the payment.
await fetch(
  `https://api.tesser.xyz/v1/payments/${step.payment_id}/steps/${step.id}/sign`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({ signature }),
  }
);
```

  </TabItem>
  <TabItem label="Kotlin">

```kotlin
import kotlinx.coroutines.runBlocking
import xyz.tesser.sdk.LocalSigner
import xyz.tesser.sdk.SigningConfig
import xyz.tesser.sdk.StepForSigning

val signer = LocalSigner(
  SigningConfig(
    publicKey = System.getenv("SIGNING_PUBLIC_KEY"),
    privateKey = System.getenv("SIGNING_PRIVATE_KEY"),
    enclaveId = System.getenv("SIGNING_ENCLAVE_ID"),
  ),
)

runBlocking {
  // `step` is the step.signature_requested webhook's data.object (parsed).
  // `fetchAccount` / `submitSignature` wrap your own HTTP client.
  val signWith = fetchAccount(step.estimated.from.accountId).cryptoWalletAddress

  val toSign = StepForSigning(
    id = step.id,
    transferId = step.paymentId,
    unsignedTransaction = step.unsignedTransaction,
    signWith = signWith,
    network = step.estimated.from.network,
  )

  val signed = signer.signStep(toSign)

  // POST /v1/payments/{paymentId}/steps/{stepId}/sign
  submitSignature(toSign.transferId, toSign.id, signed.signature)
}
```

  </TabItem>
</Tabs>

`signStep` returns a `signature` to send to the [Sign payment step API](/api/payments#sign-payment-step) to execute the payment, plus `metadata` (`stampHeaderName`, `stampHeaderValue`, `body`) for debugging that stays client-side. (The Kotlin result also echoes `unsignedTransaction` for audit trails.)

## Payout Balance Check (`payment.balance_updated`)

Wallet payouts debit funds from a self-custodial wallet. Tesser checks the wallet's balance synchronously inside your call to the [Sign payment step API](/api/payments#sign-payment-step) — the sign endpoint validates that the wallet at `desired.from.account_id` has sufficient funds before accepting your signature. See [Balance Statuses](/overviews/funds-movement-lifecycle-and-data-model#balance-statuses) on the lifecycle overview for the full status taxonomy.

If there are sufficient funds in the wallet, you receive a success response from the sign API and two webhooks fire:

- `payment.balance_updated` with `balance_status: "reserved"` and `balance_reserved_at` populated.
- `step.signed` confirming the on-chain step was successfully signed; step `status` becomes `signed`.

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example webhook schema for stablecoin payout after successful balance check</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "payment.balance_updated",
  "created_at": "2025-12-01T09:00:01.002Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "reserved",
      "balance_reserved_at": "2025-12-01T09:00:01.000Z",
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": "0x02ed81893a85 ... 3a764000080c0",
          "fees": [],
          "provider_key": "turnkey",
          "status": "signature_requested",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:00.800Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:01.000Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example webhook schema for fiat payout after successful balance check</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "payment.balance_updated",
  "created_at": "2025-12-01T09:00:01.002Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "reserved",
      "balance_reserved_at": "2025-12-01T09:00:01.000Z",
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": "0x02ed81893a85 ... 3a764000080c0",
          "fees": [],
          "provider_key": "turnkey",
          "status": "signature_requested",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:00.800Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        },
        {
          "id": "a7b3d912-5f8e-4c23-9d6a-1e4f7b2c8a5d",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 2,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "provider",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.200Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:01.000Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example webhook schema for stablecoin payout after signing</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "step.signed",
  "created_at": "2025-12-01T09:00:01.052Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": null,
      "unsigned_transaction": null,
      "fees": [],
      "provider_key": "turnkey",
      "status": "signed",
      "status_reasons": [],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:01.050Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": "2025-12-01T09:00:01.050Z",
      "submitted_at": null,
      "confirmed_at": null,
      "completed_at": null,
      "failed_at": null
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example webhook schema for fiat payout after signing</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "step.signed",
  "created_at": "2025-12-01T09:00:01.052Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": null,
      "unsigned_transaction": null,
      "fees": [],
      "provider_key": "turnkey",
      "status": "signed",
      "status_reasons": [],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:01.050Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": "2025-12-01T09:00:01.050Z",
      "submitted_at": null,
      "confirmed_at": null,
      "completed_at": null,
      "failed_at": null
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

If the wallet has insufficient funds, the synchronous response is a 4XX with an error code, and `payment.balance_updated` fires with `balance_status: "awaiting_funds"`. The payout is queued — Tesser will republish `step.signature_requested` (and refresh `signature_requested_at`) once the wallet is funded, until `expires_at`. Repeat the signing flow with the freshly published step to retry.

<Mermaid
  chart={`
sequenceDiagram
    autonumber 10
    participant You as Your integration
    participant Tesser
    Note over You,Tesser: Prior completed actions: Risk approved,<br/>step.signature_requested received, step signed locally
    You->>Tesser: POST /v1/payments/{id}/steps/{stepId}/sign
    Note over Tesser: desired.from.account_id balance checked synchronously<br/>as part of signing execution — insufficient funds
    Tesser-->>You: 4XX error
    Tesser-->>You: webhook: type=payment.balance_updated,<br/>balance_status=awaiting_funds, balance_reserved_at=null
    Note over Tesser: Payout queued until the wallet is funded<br/>or expires_at elapses
    alt Wallet funded before expires_at
        Tesser-->>You: webhook: type=step.signature_requested —<br/>republished, fresh signature_requested_at
        You->>Tesser: Repeat the signing flow with the freshly published step
        Tesser-->>You: webhook: type=payment.balance_updated, balance_status=reserved
        Tesser-->>You: webhook: type=step.signed
        Note over You,Tesser: Flow continues as the happy path
    else expires_at passes first
        Tesser-->>You: webhook: type=step.failed
        Tesser-->>You: webhook: type=payment.updated, balance_status=unreserved —<br/>terminal, top-level actual.* fields all null
        Note over You: Once expired, payments cannot be resurrected.<br/>Create a new payout to retry.
    end
`}
/>

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example webhook schema for stablecoin payout after failed balance check</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "payment.balance_updated",
  "created_at": "2025-12-01T09:00:01.002Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "awaiting_funds",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": "0x02ed81893a85 ... 3a764000080c0",
          "fees": [],
          "provider_key": "turnkey",
          "status": "signature_requested",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:00.800Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:01.000Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example webhook schema for fiat payout after failed balance check</summary>

```json
{
  "id": "99ea4da4-2178-4169-9e21-01d3cb4ae158",
  "type": "payment.balance_updated",
  "created_at": "2025-12-01T09:00:01.002Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "awaiting_funds",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": "0x02ed81893a85 ... 3a764000080c0",
          "fees": [],
          "provider_key": "turnkey",
          "status": "signature_requested",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:00.800Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        },
        {
          "id": "a7b3d912-5f8e-4c23-9d6a-1e4f7b2c8a5d",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 2,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "provider",
          "status": "created",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:00.200Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:01.000Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

## Payout On-Chain Processing

After successful signing, Tesser broadcasts the payment on-chain. Tesser fires `step.submitted` when it broadcasts the signed transaction; for wallet-originated steps that is an event-only marker — the persisted `status` goes directly from `signed` to `confirmed`. See [Step Statuses](/overviews/funds-movement-lifecycle-and-data-model#step-statuses) in the lifecycle overview. The blockchain `transaction_hash` is populated on the step once confirmed, alongside gas fees in the per-step `fees[]` array. If the same wallet has an earlier payment awaiting broadcast, this one queues behind it — the sign call returns 200 with `transaction_hash: null` and the step stays `signed` until its turn; see [signing a wallet step](/how-tos/sign-a-wallet-step).

:::note
Tesser sponsors gas fees for your organization, so on-chain transfers will not fail due to insufficient native-token balance in the wallet. Your organization is billed at the end of the month for accrued gas fees.
:::

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example webhook schema for stablecoin payout after blockchain network confirmation</summary>

```json
{
  "id": "d7e80c94-d7b3-4e9b-8c00-065e7cbcfac8",
  "type": "step.confirmed",
  "created_at": "2025-12-01T09:00:01.802Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": "0xd65dc6bf6dcc ... e877f7be",
      "unsigned_transaction": null,
      "fees": [
        {
          "fee_amount": "0.01",
          "fee_currency": "USDC",
          "fee_type": "gas",
          "fee_metadata": {}
        }
      ],
      "provider_key": "turnkey",
      "status": "confirmed",
      "status_reasons": [],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:01.800Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": "2025-12-01T09:00:01.050Z",
      "submitted_at": "2025-12-01T09:00:01.200Z",
      "confirmed_at": "2025-12-01T09:00:01.800Z",
      "completed_at": null,
      "failed_at": null
    }
  }
}
```

</details>

Note: at `step.confirmed`, the per-step `actual.*` is still all-null. Step-level `actual.{from,to}` becomes populated at `step.completed` — the design defers population to the terminal step state to minimize the risk of reporting values that subsequently change.

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example webhook schema for fiat payout after blockchain network confirmation</summary>

```json
{
  "id": "d7e80c94-d7b3-4e9b-8c00-065e7cbcfac8",
  "type": "step.confirmed",
  "created_at": "2025-12-01T09:00:01.802Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": "0x2b3c4d5e6f0a ... 3f4a5b6c",
      "unsigned_transaction": null,
      "fees": [
        {
          "fee_amount": "0.01",
          "fee_currency": "USDC",
          "fee_type": "gas",
          "fee_metadata": {}
        }
      ],
      "provider_key": "turnkey",
      "status": "confirmed",
      "status_reasons": [],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:01.800Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": "2025-12-01T09:00:01.050Z",
      "submitted_at": "2025-12-01T09:00:01.200Z",
      "confirmed_at": "2025-12-01T09:00:01.800Z",
      "completed_at": null,
      "failed_at": null
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

## Payout Fiat Step Processing

Tesser opens the off-ramp transaction with the provider as soon as the destination account is known — at creation, or when you PATCH the payment with account ids — supplying the `desired.to.account_id` beneficiary details against the quote and requesting the deposit address that the on-chain leg sends the USDC to. That deposit address is the wallet Tesser screens against your risk policy (see [Payout Risk Review](#payout-risk-review-paymentrisk_updated)). Requesting it moves no funds and does not depend on screening — the on-chain transfer to the provider only runs once the payout is risk-approved. The provider then delivers the fiat once those funds land in its wallet. That last-mile transfer appears as the final step in the route and progresses through the same step-status path — see [Step Statuses](/overviews/funds-movement-lifecycle-and-data-model#step-statuses).

:::note
The fiat-leg step's `transaction_hash` is always `null` — the provider's last-mile transfer happens within the provider's books and never touches a chain.
:::

## Payout Terminal State

When the last step reaches `completed`, Tesser populates the top-level `actual.*` overlay and emits a `payment.updated` webhook carrying the full updated payment object. Use this event to observe the terminal outcome without polling. See [Terminal State and Divergence](/overviews/funds-movement-lifecycle-and-data-model#terminal-state-and-divergence) for the full picture, including how `actual.*` may diverge from `desired.*` on failure.

For payouts that used the two-step create-then-PATCH pattern, this is the **second** `payment.updated` event you receive — the first fired after the PATCH supplied account ids (see [Update the payout with account information](#update-the-payout-with-account-information)).

<Tabs>
  <TabItem label="Stablecoin">

<details className="collapsible-example">
<summary>Example terminal-state `payment.updated` webhook for a successful stablecoin payout</summary>

```json
{
  "id": "f4c8e1a3-9b76-4d2e-a058-3c5f9e1d4b27",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:02.502Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "reserved",
      "balance_reserved_at": "2025-12-01T09:00:01.000Z",
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "transaction_hash": "0xd65dc6bf6dcc ... e877f7be",
          "unsigned_transaction": null,
          "fees": [
            {
              "fee_amount": "0.01",
              "fee_currency": "USDC",
              "fee_type": "gas",
              "fee_metadata": {}
            }
          ],
          "provider_key": "turnkey",
          "status": "completed",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:02.500Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": "2025-12-01T09:00:01.050Z",
          "submitted_at": "2025-12-01T09:00:01.200Z",
          "confirmed_at": "2025-12-01T09:00:01.800Z",
          "completed_at": "2025-12-01T09:00:02.500Z",
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:02.500Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
  <TabItem label="Fiat">

<details className="collapsible-example">
<summary>Example terminal-state `payment.updated` webhook for a successful fiat payout</summary>

```json
{
  "id": "f4c8e1a3-9b76-4d2e-a058-3c5f9e1d4b27",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:02.502Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "actual": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "reserved",
      "balance_reserved_at": "2025-12-01T09:00:01.000Z",
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "transaction_hash": "0x2b3c4d5e6f0a ... 3f4a5b6c",
          "unsigned_transaction": null,
          "fees": [
            {
              "fee_amount": "0.01",
              "fee_currency": "USDC",
              "fee_type": "gas",
              "fee_metadata": {}
            }
          ],
          "provider_key": "turnkey",
          "status": "completed",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:02.000Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": "2025-12-01T09:00:01.050Z",
          "submitted_at": "2025-12-01T09:00:01.200Z",
          "confirmed_at": "2025-12-01T09:00:01.800Z",
          "completed_at": "2025-12-01T09:00:02.000Z",
          "failed_at": null
        },
        {
          "id": "a7b3d912-5f8e-4c23-9d6a-1e4f7b2c8a5d",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 2,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "actual": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [
            {
              "fee_amount": "1.50",
              "fee_currency": "USDC",
              "fee_type": "provider",
              "fee_metadata": {}
            }
          ],
          "provider_key": "provider",
          "status": "completed",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:02.500Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": "2025-12-01T09:00:02.100Z",
          "confirmed_at": "2025-12-01T09:00:02.300Z",
          "completed_at": "2025-12-01T09:00:02.500Z",
          "failed_at": null
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:02.500Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

  </TabItem>
</Tabs>

## Failure Modes for Wallet Payouts

If a wallet payout fails, the top-level `actual.*` fields populate only when at least one step has reached `step.status = completed`. With one or more completed steps, the top-level `actual.from` matches the first completed step's `actual.from`, and the top-level `actual.to` matches the last completed step's `actual.to`. With no completed step, the top-level `actual.*` stays all-null — `desired.*` and `estimated.*` describe the originally requested and quoted state, while step-level `status_reasons` carries the cause of the failure. Failed steps always have all-null `actual.*`. Any steps subsequent to the failure step also transition to `failed` with null `actual.*` fields.

### Awaiting funds

If the wallet has insufficient funds when you submit the signature, the payout enters `balance_status: "awaiting_funds"` and waits until the wallet is funded or the payout expires. See [Payout Balance Check](#payout-balance-check-paymentbalance_updated) above for the full description and webhook example payloads.

### Risk rejection

If risk review returns a rejected outcome — either `risk_status: "automatically_rejected"` (policy-driven automatic rejection) or `risk_status: "manually_rejected"` (rejected after manual review) — the payout transitions to a terminal failed state before any signing is requested. No funds move; planned steps go directly from `created` to `failed`.

What you will observe:

- A `payment.risk_updated` webhook fires with `risk_status: "automatically_rejected"` or `"manually_rejected"` and `risk_status_reasons` populated with the reason codes. Top-level `actual.*` is all null (no movement occurred).
- Each step in `steps[]` transitions to `status: "failed"` with `actual.*` all null. Step webhook events will fire.
- The payout is terminal in this state. To proceed, resolve the underlying risk factors and create a new payout.

<details className="collapsible-example">
<summary>Example terminal `payment.updated` webhook for an auto-rejected stablecoin payout</summary>

```json
{
  "id": "a8b3d2e7-4c91-4f5a-9d28-1e7c3f8a5b04",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:00.500Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_rejected",
      "risk_status_reasons": [
        {
          "risk_status_reason_type": "COUNTERPARTY",
          "risk_status_reason_category": "Sanctions",
          "risk_status_reason_message": "Address has COUNTERPARTY exposure to Sanctions",
          "risk_status_reason_severity": "Severe",
          "risk_status_reason_participant_id": null,
          "risk_status_reason_significance": "DECISIVE",
          "volume_usd_percent": null,
          "volume_usd_percent_threshold": null,
          "volume_usd_amount": null,
          "volume_usd_amount_threshold": null,
          "confidence_score": null,
          "confidence_score_threshold": null
        }
      ],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": [
              {
                "category": "Sanctions",
                "categoryId": "69",
                "categoryRiskScoreLevel": 15,
                "categoryRiskScoreLevelLabel": "Severe",
                "incomingInstanceCount": null,
                "incomingVolumeUsd": null,
                "outgoingInstanceCount": "22",
                "outgoingVolumeUsd": "15578.90",
                "riskType": "COUNTERPARTY",
                "totalInstanceCount": "22",
                "totalVolumeUsd": "15578.90",
                "incomingVolumePercent": null,
                "outgoingVolumePercent": "0.000962",
                "totalVolumePercent": "0.000468"
              }
            ]
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "failed",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:00.500Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": "2025-12-01T09:00:00.500Z"
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:00.500Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

### Step failure

A signed step can fail after broadcast, or a downstream provider step can fail after a customer-signed step completes.

**Scenario 1 — On-chain broadcast failure**

If the on-chain transfer step is signed and broadcast but fails to confirm (chain reorg, insufficient gas, or other broadcast issue), the step transitions to `failed`. Per the failed-step rule, the step's `actual.*` is all null and `status_reasons` carries the failure detail. Top-level `actual.*` is all null because no step reached `completed` status (broadcast does not count as completion).

What you will observe:

- A `step.failed` webhook fires for the on-chain step. Step-level `actual.*` is null; `status_reasons` carries the failure detail. `submitted_at` is populated (Tesser handed off the broadcast attempt); `confirmed_at`, `completed_at`, and `transaction_hash` are null because the step never reached `step.confirmed`. `failed_at` is populated.
- A terminal `payment.updated` webhook follows. Top-level `actual.*` is all null because no step completed. `balance_status` rolls back to `"unreserved"` and `balance_reserved_at` clears to `null` — the reservation that was held at step signing is released so the wallet's balance is no longer locked against this payout.

<Mermaid
  chart={`
sequenceDiagram
    autonumber 14
    participant You as Your integration
    participant Tesser
    participant Chain as Blockchain network
    Note over You,Tesser: Prior completed actions: Signature accepted,<br/>balance reserved, step.signed received
    Tesser->>Chain: Broadcast signed transaction
    Tesser-->>You: webhook: type=step.submitted — indicates timestamp<br/>when Tesser broadcasted transaction on chain
    Chain--xTesser: Fails to confirm — reorg, insufficient gas, or broadcast issue
    Tesser-->>You: webhook: type=step.failed — status_reasons[] carry the error,<br/>transaction_hash null, step actual.* fields null
    Tesser-->>You: webhook: type=payment.updated — terminal
    Note over You,Tesser: Top-level actual.* fields all null. balance_status rolls back<br/>to unreserved, balance_reserved_at clears
`}
/>

<details className="collapsible-example">
<summary>Example `step.failed` webhook for an on-chain broadcast failure</summary>

```json
{
  "id": "c2f8e1a4-7b5d-4936-a0c8-3e9f1d2b4a7c",
  "type": "step.failed",
  "created_at": "2025-12-01T09:00:01.900Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440100",
      "payment_id": "550e8400-e29b-41d4-a716-446655440020",
      "step_sequence": 1,
      "step_type": "transfer",
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "transaction_hash": null,
      "unsigned_transaction": null,
      "fees": [],
      "provider_key": "turnkey",
      "status": "failed",
      "status_reasons": [
        {
          "error_code": "transfers-9305",
          "error_message": "The on-chain transaction failed to broadcast"
        }
      ],
      "created_at": "2025-12-01T09:00:00.400Z",
      "updated_at": "2025-12-01T09:00:01.900Z",
      "signature_requested_at": "2025-12-01T09:00:00.800Z",
      "signed_at": "2025-12-01T09:00:01.050Z",
      "submitted_at": "2025-12-01T09:00:01.200Z",
      "confirmed_at": null,
      "completed_at": null,
      "failed_at": "2025-12-01T09:00:01.900Z"
    }
  }
}
```

</details>

<details className="collapsible-example">
<summary>Example terminal `payment.updated` webhook for an on-chain broadcast failure</summary>

```json
{
  "id": "d4a9e3c1-6b82-4715-9e0d-2f5c8a1b3e64",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:01.902Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "failed",
          "status_reasons": [
            {
              "error_code": "transfers-9305",
              "error_message": "The on-chain transaction failed to broadcast"
            }
          ],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:01.900Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": "2025-12-01T09:00:01.050Z",
          "submitted_at": "2025-12-01T09:00:01.200Z",
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": "2025-12-01T09:00:01.900Z"
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:01.902Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

**Scenario 2 — Fiat-side failure**

If the on-chain transfer to the fiat provider succeeds but the subsequent fiat wire fails, Step 1 is `completed` and Step 2 is `failed`. The payout terminates with funds stranded at the fiat provider's holding ledger. Per the failed-step rule, Step 2's `actual.*` is all null. Per the first/last-completed rule, the top-level `actual.from` matches Step 1's `actual.from` (the wallet debit) and the top-level `actual.to` matches Step 1's `actual.to` (USDC at the fiat provider's holding ledger).

What you will observe:

- Step 1 (`step.completed`) fires normally with both `actual.*` overlays populated. The customer's wallet has been debited; the fiat provider's holding ledger has received the USDC.
- A `step.failed` webhook fires for Step 2. `actual.*` is all null; `status_reasons` carries the failure detail (e.g., bank rejection, OFAC issue).
- A terminal `payment.updated` webhook follows. Top-level `actual.from` matches Step 1's `actual.from` (USDC at the source wallet); top-level `actual.to` matches Step 1's `actual.to` (USDC at the fiat provider's holding ledger). Funds are stranded at the fiat provider until manual recovery. `balance_status` remains `"reserved"` and `balance_reserved_at` stays populated — funds actually left the wallet on Step 1, so there is no reservation rollback to the wallet's balance.

<Mermaid
  chart={`
sequenceDiagram
    autonumber 9
    participant You as Your integration
    participant Tesser
    participant Provider as Fiat off-ramp provider
    Note over You,Provider: Prior completed actions: Off-ramp transaction opened with the provider,<br/>step 1 (on-chain transfer to the provider deposit address) completed
    Tesser-->>You: webhook: type=step.submitted — step 2
    Provider--xTesser: Fiat payout fails — bank rejection, OFAC issue
    Tesser-->>You: webhook: type=step.failed — step 2 actual.* fields null,<br/>status_reasons[] carries the payout error information
    Tesser-->>You: webhook: type=payment.updated — terminal, top-level actual.* fields populated<br/>indicating that funds reside with the off-ramp provider
    Note over Provider: Exception handling process to recover funds or rectify issues<br/>so payout to beneficiary can proceed
`}
/>

<details className="collapsible-example">
<summary>Example terminal `payment.updated` webhook for a fiat-side failure</summary>

```json
{
  "id": "e9d4a7b2-3c6f-4815-b9e2-5d8c1f3a7b04",
  "type": "payment.updated",
  "created_at": "2025-12-01T09:00:03.100Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
          "amount": "1000",
          "currency": "MXN",
          "network": null
        }
      },
      "actual": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
          "amount": "55.85",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "reserved",
      "balance_reserved_at": "2025-12-01T09:00:01.000Z",
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
              "amount": "55.85",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "transaction_hash": "0xd65dc6bf6dcc ... e877f7be",
          "unsigned_transaction": null,
          "fees": [
            {
              "fee_amount": "0.01",
              "fee_currency": "USDC",
              "fee_type": "gas",
              "fee_metadata": {}
            }
          ],
          "provider_key": "turnkey",
          "status": "completed",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T09:00:02.500Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": "2025-12-01T09:00:01.050Z",
          "submitted_at": "2025-12-01T09:00:01.200Z",
          "confirmed_at": "2025-12-01T09:00:01.800Z",
          "completed_at": "2025-12-01T09:00:02.500Z",
          "failed_at": null
        },
        {
          "id": "a7b3d912-5f8e-4c23-9d6a-1e4f7b2c8a5d",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 2,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "f3a8b2c1-7d4e-4f9a-b6e5-2c8d9a1f0e3b",
              "amount": "55.85",
              "currency": "USDC",
              "network": null
            },
            "to": {
              "account_id": "a3f7c891-bd42-4e19-9c5a-2d8b6f3e1047",
              "amount": "1000",
              "currency": "MXN",
              "network": null
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "provider",
          "status": "failed",
          "status_reasons": [
            {
              "error_code": "transfers-9302",
              "error_message": "The provider payout failed"
            }
          ],
          "created_at": "2025-12-01T09:00:00.040Z",
          "updated_at": "2025-12-01T09:00:03.100Z",
          "signature_requested_at": null,
          "signed_at": null,
          "submitted_at": "2025-12-01T09:00:02.700Z",
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": "2025-12-01T09:00:03.100Z"
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T09:00:03.100Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>

### Expiration before completion

Every payout has an `expires_at` timestamp. If the payout has not reached terminal state by then, it cannot be resumed — to retry, create a new payout. See [Expiration](/overviews/funds-movement-lifecycle-and-data-model#expiration) for the full semantics.

What you will observe:

- A `step.failed` webhook fires for each step that didn't reach terminal state. Each failed step's `actual.*` is all null; `status_reasons` is `[]` because the step never started — the failure cause lives at the resource level (the payment timed out after remaining in `awaiting_funds` until `expires_at`).
- A `payment.updated` webhook fires reporting the final state. Top-level `actual.*` is all null because no movement occurred. `balance_status` rolls back to `"unreserved"` and `balance_reserved_at` clears to `null` — the reservation held during `awaiting_funds` is released at terminal failure.

<details className="collapsible-example">
<summary>Example terminal `payment.updated` webhook for a payout that expired while `awaiting_funds`</summary>

```json
{
  "id": "b7e3c1f2-9d48-4a56-8c0f-2e9d4a7b1f30",
  "type": "payment.updated",
  "created_at": "2025-12-01T23:59:59.999Z",
  "data": {
    "object": {
      "id": "550e8400-e29b-41d4-a716-446655440020",
      "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
      "organization_reference_id": "ref_123",
      "direction": "outbound",
      "funding_account_id": "550e8400-e29b-41d4-a716-446655440010",
      "desired": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": null,
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "estimated": {
        "from": {
          "account_id": "550e8400-e29b-41d4-a716-446655440011",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        },
        "to": {
          "account_id": "550e8400-e29b-41d4-a716-446655440012",
          "amount": "1000",
          "currency": "USDC",
          "network": "ETHEREUM"
        }
      },
      "actual": {
        "from": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        },
        "to": {
          "account_id": null,
          "amount": null,
          "currency": null,
          "network": null
        }
      },
      "risk_status": "automatically_approved",
      "risk_status_reasons": [],
      "participants": [],
      "provider_metadata": [
        {
          "provider_metadata_type": "WALLET_RISK_SCREENING",
          "provider_metadata_key": "TRM_LABS",
          "provider_metadata_data": {
            "address": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
            "entities": [],
            "addressRiskIndicators": []
          }
        }
      ],
      "risk_reviewed_by": null,
      "risk_reviewed_at": null,
      "balance_status": "unreserved",
      "balance_reserved_at": null,
      "steps": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440100",
          "payment_id": "550e8400-e29b-41d4-a716-446655440020",
          "step_sequence": 1,
          "step_type": "transfer",
          "estimated": {
            "from": {
              "account_id": "550e8400-e29b-41d4-a716-446655440011",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            },
            "to": {
              "account_id": "550e8400-e29b-41d4-a716-446655440012",
              "amount": "1000",
              "currency": "USDC",
              "network": "ETHEREUM"
            }
          },
          "actual": {
            "from": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            },
            "to": {
              "account_id": null,
              "amount": null,
              "currency": null,
              "network": null
            }
          },
          "transaction_hash": null,
          "unsigned_transaction": null,
          "fees": [],
          "provider_key": "turnkey",
          "status": "failed",
          "status_reasons": [],
          "created_at": "2025-12-01T09:00:00.400Z",
          "updated_at": "2025-12-01T23:59:59.999Z",
          "signature_requested_at": "2025-12-01T09:00:00.800Z",
          "signed_at": null,
          "submitted_at": null,
          "confirmed_at": null,
          "completed_at": null,
          "failed_at": "2025-12-01T23:59:59.999Z"
        }
      ],
      "created_at": "2025-12-01T09:00:00.000Z",
      "updated_at": "2025-12-01T23:59:59.999Z",
      "expires_at": "2025-12-01T23:59:59.999Z"
    }
  }
}
```

</details>
