> ## Documentation Index
> Fetch the complete documentation index at: https://turnkey-0e7c1f5b-traian-remove-eip-712-note.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive signed, real-time notifications for events in your Turnkey organization.

Webhooks deliver real-time notifications as signed HTTPS POST requests. Register an endpoint and subscribe to event types, and Turnkey will automatically deliver updates as they occur.

* Signed deliveries with Ed25519 signatures -- see [Verify signatures](/features/webhooks/verify-signatures)
* Organization-aware headers including org ID, event type, and timestamps on every delivery
* Automatic retries for failed deliveries
* Dashboard and API management for creating and configuring endpoints
* Policy-based access control through dedicated activity types

## Event types

| Event type                        | Description                                                                                                                                                                              | Scope                       |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `ACTIVITY_UPDATES`                | Sends activity status updates. Parent-owned endpoints receive events for the parent and all sub-organizations; sub-organization-owned endpoints receive only their own events.           | Organization-scoped         |
| `BALANCE_CONFIRMED_UPDATES`       | Sends confirmed balance update events when a transaction containing a balance change is first seen in a block onchain.                                                                   | Billing organization scoped |
| `BALANCE_FINALIZED_UPDATES`       | Sends finalized balance update events when the containing block has reached the finalization threshold. Add this alongside `BALANCE_CONFIRMED_UPDATES` if you need finalization signals. | Billing organization scoped |
| `SEND_TRANSACTION_STATUS_UPDATES` | Sends transaction status updates when a transaction changes state (e.g. from `BROADCASTING` to `INCLUDED` or `FAILED`).                                                                  | Billing organization scoped |

<Note>
  Balance and transaction status webhook endpoints must be managed from the billing organization. Sub-organization attempts to create, update, or delete these endpoints return `PermissionDenied`.

  Only documented event types produce deliveries. Unknown event types should not be used and may be rejected in the future.

  For further information on balances, including supported chains and assets, see [Balances](/features/transaction-management/balances).
</Note>

## Create an endpoint

Create webhook endpoints from a server-side client using an API key. The endpoint URL must be HTTPS and must resolve to a public destination.

SDK methods accept the intent parameters directly. The SDK adds the activity envelope fields (`type`, `timestampMs`, `organizationId`, and `parameters`) before signing and submitting the request. Use the raw envelope shape only when calling the HTTP API directly.

<Note>
  For server-side/API-key automation, use `@turnkey/sdk-server@6.1.0+`; it includes `createWebhookEndpoint`.

  For client-side admin flows, `@turnkey/core` can also submit webhook endpoint activities through `client.httpClient.createWebhookEndpoint(...)`, provided the authenticated session is authorized to submit signed activities for the organization.

  Raw HTTP and CLI submission remain supported for direct activity submission. SDK methods accept intent parameters directly; raw HTTP uses the activity envelope shape.
</Note>

### Activity updates

```ts theme={null}
import { Turnkey } from "@turnkey/sdk-server";

const organizationId = process.env.ORGANIZATION_ID!;
const webhookUrl = "https://example.com/webhooks/turnkey";

const turnkey = new Turnkey({
  apiBaseUrl: "https://api.turnkey.com",
  apiPublicKey: process.env.API_PUBLIC_KEY!,
  apiPrivateKey: process.env.API_PRIVATE_KEY!,
  defaultOrganizationId: organizationId,
});

const activityWebhook = await turnkey.apiClient().createWebhookEndpoint({
  organizationId, // optional if defaultOrganizationId is configured
  name: "Activity updates",
  url: webhookUrl,
  subscriptions: [{ eventType: "ACTIVITY_UPDATES" }],
});
```

### Balance updates

For balance webhooks, subscribe to `BALANCE_CONFIRMED_UPDATES` when enabling balance notifications. Add `BALANCE_FINALIZED_UPDATES` alongside confirmed updates if you also need finalization signals.

```ts theme={null}
const organizationId = process.env.ORGANIZATION_ID!;
const webhookUrl = "https://example.com/webhooks/balances";

const balanceWebhook = await turnkey.apiClient().createWebhookEndpoint({
  organizationId,
  name: "Balance confirmed updates",
  url: webhookUrl,
  subscriptions: [{ eventType: "BALANCE_CONFIRMED_UPDATES" }],
});
```

<Warning>
  The `name` field is a human-readable endpoint name and should be non-empty. Event types must be passed in `subscriptions[]`; do not pass a top-level `eventTypes` field.
</Warning>

## Manage endpoints

Use the webhook endpoint APIs or the Dashboard UI to manage existing endpoints:

| Operation                                                            | Path                                        | Notes                                                             |
| -------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------- |
| [Create endpoint](/api-reference/activities/create-webhook-endpoint) | `/public/v1/submit/create_webhook_endpoint` | Requires `url` and `subscriptions[]`; `name` should be non-empty. |
| [Update endpoint](/api-reference/activities/update-webhook-endpoint) | `/public/v1/submit/update_webhook_endpoint` | Updates `url`, `name`, or `isActive`.                             |
| [Delete endpoint](/api-reference/activities/delete-webhook-endpoint) | `/public/v1/submit/delete_webhook_endpoint` | Deletes an endpoint and its subscriptions.                        |
| [List endpoints](/api-reference/queries/list-webhook-endpoints)      | `/public/v1/query/list_webhook_endpoints`   | Returns endpoints and their subscriptions for an organization.    |

Set `isActive` to `false` to pause delivery without deleting the endpoint.

## Endpoint validation and reachability

Webhook endpoint URLs are validated when endpoints are created or updated, and delivery also uses dial-time protections. URLs must use `https`, include a valid host, and resolve to a public destination. Turnkey rejects URLs that point to localhost, private IP ranges, link-local addresses, metadata endpoints, or URLs that include user info.

Redirects are not followed. If your endpoint hostname later resolves to a disallowed destination, delivery will fail even if the endpoint was valid when it was created.

Keep your endpoint publicly reachable and return a `2xx` response after accepting the webhook. Avoid long-running request handling in the delivery path; enqueue work internally and respond quickly. `3xx`, `4xx`, and `429` responses are treated as terminal delivery failures, while network errors and `5xx` responses may be retried.

## Delivery contract

Turnkey sends each webhook as an HTTPS `POST` request. The request body is JSON and the `Content-Type` header is `application/json`. Your endpoint should return a `2xx` status code after accepting the delivery. Only active endpoints and active subscriptions receive deliveries.

### Headers

| Header                          | Description                                                                                                                                                                                                                          |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-Turnkey-Organization-Id`     | Organization used for webhook routing and delivery. For billing-scoped events such as balance and transaction status updates, this is the billing/parent organization. The event owner is available in the payload `organizationId`. |
| `X-Turnkey-Event-Type`          | Event type, such as `ACTIVITY_UPDATES` or `BALANCE_CONFIRMED_UPDATES`.                                                                                                                                                               |
| `X-Turnkey-Timestamp`           | Unix timestamp in milliseconds for the delivery attempt.                                                                                                                                                                             |
| `X-Turnkey-Webhook-Version`     | Webhook delivery contract version. The current value is `1`.                                                                                                                                                                         |
| `X-Turnkey-Event-Id`            | Signed delivery metadata. This value is stable across retry attempts for the same webhook event.                                                                                                                                     |
| `X-Turnkey-Signature-Key-Id`    | Identifier for the Turnkey signing key.                                                                                                                                                                                              |
| `X-Turnkey-Signature-Algorithm` | Signature algorithm. The current value is `ed25519`.                                                                                                                                                                                 |
| `X-Turnkey-Signature-Version`   | Signature contract version. The current value is `v1`.                                                                                                                                                                               |
| `X-Turnkey-Signature`           | Hex-encoded Ed25519 signature.                                                                                                                                                                                                       |

### Retry behavior

Turnkey treats `2xx` responses as successful. Turnkey automatically retries retryable delivery failures. Retry schedules and attempt counts are subject to change.

Signed retries receive a fresh timestamp and signature. `X-Turnkey-Event-Id` is signed delivery metadata and is stable across retry attempts for the same webhook event. Payload fields such as `msg.idempotencyKey` are event-specific business identifiers. Either may be useful for deduplication depending on the use case, but they are not the same field.

## Payloads

### Activity updates

`ACTIVITY_UPDATES` deliveries contain the full activity object for the triggering event. Use the activity `id` and/or the webhook `X-Turnkey-Event-Id` header to process deliveries idempotently.

### Balance updates

Each delivery corresponds to a single balance-change event: one address, one operation (`deposit` or `withdraw`), and one asset. A single transaction can affect multiple addresses or assets, so it may produce multiple webhook deliveries, each with its own `idempotencyKey`.

The `type` field is `"balances:confirmed"` when a balance change is first seen onchain, or `"balances:finalized"` when the associated block has reached the finalization threshold.

```json theme={null}
{
  "type": "balances:confirmed",
  "organizationId": "<organization-id>",
  "parentOrganizationId": "<parent-organization-id>",
  "msg": {
    "operation": "deposit",
    "caip2": "<chain-id>",
    "txHash": "<transaction-hash>",
    "address": "<wallet-address>",
    "idempotencyKey": "<idempotency-key>",
    "asset": {
      "symbol": "<asset-symbol>",
      "name": "<asset-name>",
      "decimals": "<asset-decimals>",
      "caip19": "<asset-caip19>",
      "amount": "<amount>"
    },
    "block": {
      "number": "<block-number>",
      "hash": "<block-hash>",
      "timestamp": "<block-timestamp>"
    }
  }
}
```

| Field                  | Description                                                                                                                      |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `type`                 | `"balances:confirmed"` when first seen onchain, or `"balances:finalized"` when the block has reached the finalization threshold. |
| `organizationId`       | Organization that owns the address.                                                                                              |
| `parentOrganizationId` | Billing/parent organization that owns webhook configuration and delivery.                                                        |
| `msg.operation`        | Either `"deposit"` (incoming) or `"withdraw"` (outgoing).                                                                        |
| `msg.caip2`            | The chain identifier where the event occurred.                                                                                   |
| `msg.txHash`           | The transaction hash that triggered the balance change.                                                                          |
| `msg.address`          | The address whose balance changed.                                                                                               |
| `msg.idempotencyKey`   | A stable, unique key for this event. Use this to safely deduplicate webhook deliveries.                                          |
| `msg.asset`            | Asset metadata: symbol, name, decimals, CAIP-19 identifier, and the amount transferred (in the asset's smallest unit).           |
| `msg.block`            | Block number, hash, and timestamp of the block in which the transaction was first seen.                                          |

<Note>
  Balance webhooks fire only for assets in the [supported asset list](/api-reference/queries/list-supported-assets) and are not supported for private key addresses.
</Note>

### Transaction status updates

Each delivery fires when a transaction changes state. The `type` is always `"transaction:status"`. Fields present in `msg` depend on the status:

* **BROADCASTING**: base fields only, no `txHash` or `error`
* **INCLUDED**: base fields + `txHash`. If the transaction reverted onchain, `error` is also present.
* **FAILED**: base fields + `error`. No `txHash` (the transaction never landed onchain).

```json theme={null}
{
  "type": "transaction:status",
  "organizationId": "<organization-id>",
  "parentOrganizationId": "<parent-organization-id>",
  "msg": {
    "sendTransactionStatusId": "<send-transaction-status-id>",
    "activityId": "<activity-id>",
    "status": "INCLUDED",
    "caip2": "<chain-id>",
    "idempotencyKey": "<idempotency-key>",
    "timestamp": "<unix-timestamp>",
    "txHash": "<transaction-hash>"
  }
}
```

| Field                         | Description                                                                                                   |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `type`                        | Always `"transaction:status"`.                                                                                |
| `organizationId`              | Organization that initiated the transaction.                                                                  |
| `parentOrganizationId`        | Billing/parent organization that owns webhook configuration and delivery.                                     |
| `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. |

For more details on transaction broadcasting, see [Broadcasting](/features/transaction-management/broadcasting).

## Permissions

Creating, updating, and deleting webhook endpoints are standard Turnkey write activities. Root users can approve them by default. Use [Turnkey policies](/features/policies/overview) to delegate webhook management to non-root users.

```text theme={null}
activity.type == 'ACTIVITY_TYPE_CREATE_WEBHOOK_ENDPOINT'
activity.type == 'ACTIVITY_TYPE_UPDATE_WEBHOOK_ENDPOINT'
activity.type == 'ACTIVITY_TYPE_DELETE_WEBHOOK_ENDPOINT'
```

Read operations, such as listing webhook endpoints, use standard authenticated query access.

## Troubleshooting

| Symptom                                            | What to check                                                                                                                                                                                                                                                 |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `createWebhookEndpoint` is unavailable in your SDK | Use `@turnkey/sdk-server@6.1.0+` for server-side/API-key automation. Use `@turnkey/core` for browser/client-side admin flows.                                                                                                                                 |
| `PermissionDenied` on create/update/delete         | Confirm the user has an allow policy for the webhook activity type. For balance or transaction-status webhooks, also confirm the endpoint is being managed from the billing organization.                                                                     |
| Subscription shape errors                          | Pass event types inside `subscriptions[]`, not as top-level `eventTypes`. For raw HTTP activity submission, put `subscriptions[]` inside `parameters`.                                                                                                        |
| Empty endpoint names                               | Set a non-empty, human-readable `name`. For raw HTTP activity submission, set `parameters.name`.                                                                                                                                                              |
| Invalid webhook URL errors                         | Use an HTTPS URL that resolves to a public destination. Localhost, private IPs, link-local addresses, metadata endpoints, and URLs with user info are rejected.                                                                                               |
| Signature verification fails                       | See [Verify webhook signatures](/features/webhooks/verify-signatures). Common causes: not using the exact raw request body, clock skew, or a missing/stale JWKS key. Cache JWKS according to `Cache-Control` and refetch when the signature `kid` is unknown. |
