Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.turnkey.com/llms.txt

Use this file to discover all available pages before exploring further.

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
  • 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 typeDescriptionScope
ACTIVITY_UPDATESSends 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_UPDATESSends confirmed balance update events when a transaction containing a balance change is first seen in a block onchain.Billing organization scoped
BALANCE_FINALIZED_UPDATESSends 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_UPDATESSends transaction status updates when a transaction changes state (e.g. from BROADCASTING to INCLUDED or FAILED).Billing organization scoped
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.

Create an endpoint

Create webhook endpoints from a server-side client using an API key, or from any Turnkey client that can submit signed activities for your organization. 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.

Activity updates

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.
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" }],
});
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.

Manage endpoints

Use the webhook endpoint APIs or the Dashboard UI to manage existing endpoints:
OperationPathNotes
Create endpoint/public/v1/submit/create_webhook_endpointRequires url and subscriptions[]; name should be non-empty.
Update endpoint/public/v1/submit/update_webhook_endpointUpdates url, name, or isActive.
Delete endpoint/public/v1/submit/delete_webhook_endpointDeletes an endpoint and its subscriptions.
List endpoints/public/v1/query/list_webhook_endpointsReturns 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

HeaderDescription
X-Turnkey-Organization-IdOrganization 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-TypeEvent type, such as ACTIVITY_UPDATES or BALANCE_CONFIRMED_UPDATES.
X-Turnkey-TimestampUnix timestamp in milliseconds for the delivery attempt.
X-Turnkey-Webhook-VersionWebhook delivery contract version. The current value is 1.
X-Turnkey-Event-IdSigned delivery metadata. This value is stable across retry attempts for the same webhook event.
X-Turnkey-Signature-Key-IdIdentifier for the Turnkey signing key.
X-Turnkey-Signature-AlgorithmSignature algorithm. The current value is ed25519.
X-Turnkey-Signature-VersionSignature contract version. The current value is v1.
X-Turnkey-SignatureHex-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.

Verify signatures

Verify the signature before parsing or trusting the webhook body. Signature verification requires the exact raw request body bytes that Turnkey sent. Re-serializing parsed JSON, changing whitespace, or changing key order will cause verification to fail. The signed message follows this format:
v1.ed25519.<signing_key_id>.<timestamp_ms>.<event_id>.<raw_body>
The event_id segment in the signed message is the value of the X-Turnkey-Event-Id header. Signature verification signs only the signature contract fields shown above plus the raw body. Other delivery headers are useful for routing and observability but are not part of the signed message. To verify a delivery, reconstruct the signed message from the headers and raw body, then verify the Ed25519 signature against the signing key identified by X-Turnkey-Signature-Key-Id. Ensure the timestamp is within an acceptable freshness window (e.g. 5 minutes) to guard against replay attacks.
An SDK verification helper and a key discovery endpoint are releasing soon to simplify this process.

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.
{
  "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>"
    }
  }
}
FieldDescription
type"balances:confirmed" when first seen onchain, or "balances:finalized" when the block has reached the finalization threshold.
organizationIdOrganization that owns the address.
parentOrganizationIdBilling/parent organization that owns webhook configuration and delivery.
msg.operationEither "deposit" (incoming) or "withdraw" (outgoing).
msg.caip2The chain identifier where the event occurred.
msg.txHashThe transaction hash that triggered the balance change.
msg.addressThe address whose balance changed.
msg.idempotencyKeyA stable, unique key for this event. Use this to safely deduplicate webhook deliveries.
msg.assetAsset metadata: symbol, name, decimals, CAIP-19 identifier, and the amount transferred (in the asset’s smallest unit).
msg.blockBlock number, hash, and timestamp of the block in which the transaction was first seen.
Balance webhooks fire only for assets in the supported asset list and are not supported for private key addresses.

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).
{
  "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>"
  }
}
FieldDescription
typeAlways "transaction:status".
organizationIdOrganization that initiated the transaction.
parentOrganizationIdBilling/parent organization that owns webhook configuration and delivery.
msg.sendTransactionStatusIdThe ID of the send transaction status record.
msg.activityIdThe ID of the originating Turnkey activity.
msg.statusOne of BROADCASTING, INCLUDED, or FAILED.
msg.caip2The chain identifier where the transaction was sent.
msg.idempotencyKeyA stable, unique key for this status event. Use this to safely deduplicate webhook deliveries.
msg.timestampUnix timestamp (seconds) when the notification was generated.
msg.txHash(INCLUDED only) The onchain transaction hash or Solana signature.
msg.errorStructured error object. Contains message, and either eth.revertChain (EVM) or solana (Solana) details.
For more details on transaction broadcasting, see Broadcasting.

Permissions

Creating, updating, and deleting webhook endpoints are standard Turnkey write activities. Root users can approve them by default. Use Turnkey policies to delegate webhook management to non-root users.
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

SymptomWhat to check
createWebhookEndpoint is unavailable in your SDKUpgrade to a webhooks-capable SDK release. The minimum SDK version will be listed in the SDK changelog once published.
PermissionDenied on create/update/deleteConfirm the user has an allow policy for the webhook activity type, and confirm balance webhooks are being managed from the billing organization.
Empty or unclear endpoint namesSet parameters.name to a non-empty, human-readable name.
Subscription shape errorsPass event types inside parameters.subscriptions[], not as top-level eventTypes.
Invalid webhook URL errorsUse 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 failsVerify against the exact raw request body bytes, use the millisecond timestamp and event id from the headers, check clock skew, and select the public key matching the signature key id.