Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 116 additions & 6 deletions concepts/broadcasting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Turnkey handles all of this for you via `solSendTransaction`. Whether or not you

## Transaction status and enriched errors

After you send a transaction, Turnkey monitors its status until it fails or is confirmed on-chain.
After you send a transaction, Turnkey monitors its status until it fails or is confirmed onchain. You can [query the transaction status](./broadcasting#querying-via-api) or subscribe to status updates [via webhooks](./broadcasting#webhooks).

### Transaction Statuses

Expand All @@ -54,19 +54,129 @@ The following statuses apply to both EVM and Solana transactions:
| ------------ | -------------------------------------------------------------------------------------------------------- |
| INITIALIZED | Turnkey has constructed and signed the transaction and prepared fees, but it has not yet been broadcast. |
| BROADCASTING | Turnkey is actively broadcasting the transaction to the network and awaiting inclusion. |
| INCLUDED | The transaction has been included in a block (EVM) or confirmed on-chain (Solana). |
| FAILED | The transaction could not be included on-chain and will not be retried automatically. |
| INCLUDED | The transaction has been included in a block (EVM) or confirmed onchain (Solana). |
| FAILED | The transaction could not be included onchain and will not be retried automatically. |

### EVM Smart Contract Transaction Errors

For EVM transactions that revert, Turnkey runs a simulation to produce structured execution traces and decode common revert reasons — giving you actionable error messages instead of opaque hex data.

| **Error type** | **Description** |
| :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
| UNKNOWN | The transaction reverted during on-chain execution or simulation, but the revert reason could not be decoded (e.g. missing ABI or unverified contract). |
| UNKNOWN | The transaction reverted during onchain execution or simulation, but the revert reason could not be decoded (e.g. missing ABI or unverified contract). |
| NATIVE | The transaction reverted due to a built-in Solidity error, such as `require()`, `assert()`, or a plain `revert()`. |
| CUSTOM | The transaction reverted due to a contract-defined custom error declared using Solidity's `error` keyword. |

<Note>
These error types describe how an EVM smart contract reverted during on-chain execution or pre-flight simulation. Turnkey application-level errors (e.g. signing failures, policy rejections) are not classified here and are instead surfaced via `Error.Message`.
</Note>
These error types describe how an EVM smart contract reverted during onchain execution or pre-flight simulation. Turnkey application-level errors (e.g. signing failures, policy rejections) are not classified here and are instead surfaced via `Error.Message`.
</Note>

### Querying via API

Use the [Get Send Transaction Status](/api-reference/queries/get-send-transaction-status) endpoint to poll for the current status of any transaction by its `sendTransactionStatusId` (returned when you call `ethSendTransaction` or `solSendTransaction`).

The response includes a `txStatus` field with the current status and, when applicable, an `error` object with the same structure as the `error` field in webhook payloads — containing a human-readable `message` and either `eth.revertChain` (for EVM reverts) or `solana` (for Solana failures) with full structured details.

### Webhooks

Turnkey Webhooks let you react to transaction status updates in real time, without polling. Instead of repeatedly calling the [Get Send Transaction Status](/api-reference/queries/get-send-transaction-status) API to check for updates, you register an endpoint and Turnkey pushes the data to you; an HTTP POST fires when a transaction status changes (e.g. from `BROADCASTING` to `INCLUDED` or `FAILED`). Webhook payloads deliver rich transaction status updates as soon as they occur, so you can keep your users informed and react to failures immediately.

You subscribe to webhooks at the parent organization level. Subscriptions cover transactions across the parent organization and all of its sub-organizations. In other words, once you subscribe you'll receive webhook notifications for all transactions that are powered by Turnkey's transaction management suite.

#### Subscribing

Use the [Create Webhook Endpoint](/api-reference/activities/create-webhook-endpoint) API with the `SEND_TRANSACTION_STATUS_UPDATES` event type to register your endpoint. Create the webhook endpoint on the parent organization.

#### Delivery payload

Each delivery is an HTTP POST with a JSON body containing a `type`, `organizationId`, `parentOrganizationId`, and a `msg` object. The `type` is always `"transaction:status"`. The fields present in `msg` depend on the status:

- **BROADCASTING**: base fields only — no `txHash`, no `error`
- **INCLUDED**: base fields + `txHash`. If the transaction was included but reverted onchain, `error` is also present with full revert details.
- **FAILED**: base fields + `error`. No `txHash` (the transaction never landed onchain).

| **Field** | **Description** |
| :------------------------ | :------------------------------------------------------------------------------------------------------------ |
| `type` | Always `"transaction:status"`. |
| `organizationId` | The organization ID that initiated the transaction. |
| `parentOrganizationId` | The parent organization ID. |
| `msg.sendTransactionStatusId` | The ID of the send transaction status record. |
| `msg.activityId` | The ID of the originating Turnkey activity. |
| `msg.status` | One of `BROADCASTING`, `INCLUDED`, or `FAILED`. |
| `msg.caip2` | The chain identifier where the transaction was sent. |
| `msg.idempotencyKey` | A stable, unique key for this status event. Use this to safely deduplicate webhook deliveries. |
| `msg.timestamp` | Unix timestamp (seconds) when the notification was generated. |
| `msg.txHash` | _(INCLUDED only)_ The onchain transaction hash or Solana signature. |
| `msg.error` | Structured error object. Contains `message`, and either `eth.revertChain` (EVM) or `solana` (Solana) details. |

**BROADCASTING**

```json
{
"type": "transaction:status",
"organizationId": "9e8d7c6b-aaaa-bbbb-cccc-ddddeeee0000",
"parentOrganizationId": "9e8d7c6b-aaaa-bbbb-cccc-ddddeeee0000",
"msg": {
"sendTransactionStatusId": "f3a2b1c0-1234-5678-abcd-ef0123456789",
"activityId": "a1b2c3d4-0000-1111-2222-333344445555",
"status": "BROADCASTING",
"caip2": "eip155:1",
"idempotencyKey": "3f4a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3",
"timestamp": 1746000000
}
}
```

**INCLUDED**

```json
{
"type": "transaction:status",
"organizationId": "9e8d7c6b-aaaa-bbbb-cccc-ddddeeee0000",
"parentOrganizationId": "9e8d7c6b-aaaa-bbbb-cccc-ddddeeee0000",
"msg": {
"sendTransactionStatusId": "f3a2b1c0-1234-5678-abcd-ef0123456789",
"activityId": "a1b2c3d4-0000-1111-2222-333344445555",
"status": "INCLUDED",
"caip2": "eip155:1",
"idempotencyKey": "7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8",
"timestamp": 1746000042,
"txHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"
}
}
```

**FAILED**

```json
{
"type": "transaction:status",
"organizationId": "9e8d7c6b-aaaa-bbbb-cccc-ddddeeee0000",
"parentOrganizationId": "9e8d7c6b-aaaa-bbbb-cccc-ddddeeee0000",
"msg": {
"sendTransactionStatusId": "f3a2b1c0-1234-5678-abcd-ef0123456789",
"activityId": "a1b2c3d4-0000-1111-2222-333344445555",
"status": "FAILED",
"caip2": "eip155:1",
"idempotencyKey": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2",
"timestamp": 1746000015,
"error": {
"message": "Execution reverted on chain: insufficient balance for transfer",
"eth": {
"revertChain": [
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"errorType": "native",
"nativeType": "error_string",
"displayMessage": "insufficient balance for transfer"
}
]
}
}
}
}
```

<Tip>
See the [with-tx-webhooks](https://github.com/tkhq/sdk/tree/main/examples/with-tx-webhooks) SDK example for a working integration.
</Tip>
28 changes: 14 additions & 14 deletions snippets/shared/balance-concepts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Each balance entry includes the asset metadata (symbol, name, decimals, and CAIP

### Webhooks

Turnkey Webhooks let you react to balance changes in real time, without polling. Instead of repeatedly calling the Get Balances API to check for updates, you register an endpoint and Turnkey pushes the data to you; an HTTP POST fires when a supported asset transfer is first confirmed in a block onchain. Webhook payloads deliver balance diffs as soon as a transaction is confirmed in a block. Combined with the Balance APIs, you have everything you need to keep your application's balances up to date without building your own indexing/polling infrastructure or relying on another third party.
Turnkey Webhooks let you react to balance changes in real time, without polling. Instead of repeatedly calling the [Get Balances](/api-reference/queries/get-balances) API to check for updates, you register an endpoint and Turnkey pushes the data to you; an HTTP POST fires when a supported asset transfer is first confirmed in a block onchain. Webhook payloads deliver balance diffs as soon as a transaction is confirmed in a block. Combined with the Balance APIs, you have everything you need to keep your application's balances up to date without building your own indexing/polling infrastructure or relying on another third party.

You subscribe to webhooks at the parent organization level. Subscriptions cover wallet-account addresses across the parent organization and all of its sub-organizations. In other words, once you subscribe you'll receive webhook notifications for all the addresses within your entire Turnkey instance.

Expand Down Expand Up @@ -93,13 +93,13 @@ Example:
```json
{
"type": "balances:confirmed",
"organizationId": "7298cfe0-4dab-40a2-afeb-4a4c2eaaffff",
"parentOrganizationId": "7298cfe0-4dab-40a2-afeb-4a4c2eaaffff",
"msg": {
"operation": "deposit",
"caip2": "eip155:1",
"txHash": "0x1e126f617337ba40dd3f5f3f98047077364f489711bf6c954ebe86e38d89ffff",
"address": "0x51e217d00473aaf5bcd7f741ee3c88c865fdffff",
"orgID": "7298cfe0-4dab-40a2-afeb-4a4c2eaaffff",
"parentOrgID": "7298cfe0-4dab-40a2-afeb-4a4c2eaaffff",
"idempotencyKey": "e3ab173e2cce8ef889eb8aba5828815b863c104b696ceac42935faebf8da01a0",
"asset": {
"symbol": "USDC",
Expand All @@ -119,19 +119,19 @@ Example:

| **Field** | **Description** |
| :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | Always `"balances:confirmed"` for balance change events. |
| `operation` | Either `"deposit"` (incoming) or `"withdraw"` (outgoing). |
| `caip2` | The chain identifier where the event occurred. |
| `txHash` | The transaction hash that triggered the balance change. |
| `address` | The address whose balance changed. |
| `orgID` | The organization ID that owns the address. |
| `parentOrgID` | The parent organization ID. |
| `idempotencyKey` | A stable, unique key for this event. Use this to safely deduplicate webhook deliveries. |
| `asset` | Asset metadata: symbol, name, decimals, CAIP-19 identifier, and the amount transferred (in the asset's decimals). Webhooks are emitted only for supported assets. |
| `block` | Block number, hash, and timestamp of the block in which the transaction was first seen. |
| `type` | Always `"balances:confirmed"` for balance change events. |
| `organizationId` | The organization ID that owns the address. |
| `parentOrganizationId` | The parent organization ID. |
| `operation` | Either `"deposit"` (incoming) or `"withdraw"` (outgoing). |
| `caip2` | The chain identifier where the event occurred. |
| `txHash` | The transaction hash that triggered the balance change. |
| `address` | The address whose balance changed. |
| `idempotencyKey` | A stable, unique key for this event. Use this to safely deduplicate webhook deliveries. |
| `asset` | Asset metadata: symbol, name, decimals, CAIP-19 identifier, and the amount transferred (in the asset's decimals). Webhooks are emitted only for supported assets. |
| `block` | Block number, hash, and timestamp of the block in which the transaction was first seen. |

<Tip>
See the [with-balance-confirmed-webhooks](https://github.com/tkhq/sdk/tree/main/examples/with-balance-confirmed-webhooks) SDK example for a working integration.
See the [with-tx-webhooks](https://github.com/tkhq/sdk/tree/main/examples/with-tx-webhooks) SDK example for a working integration.
</Tip>

#### Limitations
Expand Down