From bbd5e1635fff302d5038ad071417c43b6a19ac57 Mon Sep 17 00:00:00 2001 From: John Mitsch Date: Tue, 2 Jun 2026 11:39:14 -0400 Subject: [PATCH 1/2] fix(webhooks)!: mark compression and event_hashes required The webhooks REST API requires `compression` on every webhook destination and `eventHashes` on every `evmContractEvents` template, but both fields were typed `Option` in the SDK with `skip_serializing_if`. Calls that omitted them serialized without the keys and the API rejected the request at runtime (HTTP 400 `"invalid compression: ""` for compression, HTTP 500 `"Expected array for arg \"eventHashes\""` for event hashes). Tightening the types surfaces the requirement at the type system instead of at the network boundary. Verified by running the failing curls against the live API before/after and by checking the published OpenAPI spec, which lists both fields in its `required` arrays. Ripples to PyO3 `#[new]` signatures, the regenerated TS `.d.ts`, the Python `_core/__init__.pyi`, all four per-language READMEs, and example scripts. Ruby webhook methods take destination/template as JSON strings so they pick the change up via serde at deserialization time, no binding-side change needed. --- crates/core/README.md | 6 +++--- crates/core/examples/webhooks_e2e.rs | 8 +++---- crates/core/src/webhooks/mod.rs | 12 +++++------ crates/core/src/webhooks/webhook.rs | 25 ++++++++++------------ crates/node/src/lib.rs | 4 ++-- crates/node/src/webhooks_template.rs | 13 ++++-------- crates/python/src/lib.rs | 4 ++-- npm/README.md | 6 +++--- npm/examples/webhooks_e2e.ts | 2 ++ npm/index.d.ts | 16 +++++++------- python/README.md | 9 +++++--- python/examples/webhooks_e2e.py | 2 ++ python/quicknode_sdk/_core/__init__.pyi | 28 ++++++++++++------------- ruby/README.md | 4 ++-- 14 files changed, 69 insertions(+), 70 deletions(-) diff --git a/crates/core/README.md b/crates/core/README.md index 48154f1..222da01 100644 --- a/crates/core/README.md +++ b/crates/core/README.md @@ -1314,7 +1314,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | Factory | Argument struct | Fields | |---|---|---| | `evm_wallet_filter` | `EvmWalletFilterTemplate` | `wallets: string[]` | -| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `event_hashes?: string[]` | +| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `event_hashes: string[]` | | `evm_abi_filter` | `EvmAbiFilterTemplate` | `abi: string` (JSON), `contracts: string[]` | | `solana_wallet_filter` | `SolanaWalletFilterTemplate` | `accounts: string[]` | | `bitcoin_wallet_filter` | `BitcoinWalletFilterTemplate` | `wallets: string[]` | @@ -1322,7 +1322,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | `hyperliquid_wallet_events_filter` | `HyperliquidWalletEventsFilterTemplate` | `wallets: string[]` | | `stellar_wallet_transactions_filter` | `StellarWalletTransactionsFilterTemplate` | `source_accounts: string[]` | -`WebhookDestinationAttributes`: `url` (required), `security_token` (optional — auto-generated if omitted), `compression` (optional — `"none"` | `"gzip"`). +`WebhookDestinationAttributes`: `url` (required), `compression` (required — `"none"` | `"gzip"`), `security_token` (optional — auto-generated if omitted). `WebhookStartFrom`: `Last` (resume from last delivered block) or `Latest` (start from newest). @@ -1376,7 +1376,7 @@ let params = CreateWebhookFromTemplateParams { destination_attributes: WebhookDestinationAttributes { url: "https://webhook.site/...".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args, }; diff --git a/crates/core/examples/webhooks_e2e.rs b/crates/core/examples/webhooks_e2e.rs index 0e70c49..69fcd56 100644 --- a/crates/core/examples/webhooks_e2e.rs +++ b/crates/core/examples/webhooks_e2e.rs @@ -44,7 +44,7 @@ async fn main() { destination_attributes: WebhookDestinationAttributes { url: "https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args, }; @@ -102,9 +102,9 @@ async fn main() { // `event_hashes` field. The API expects `eventHashes` on the wire. let contract_events_args = TemplateArgs::EvmContractEvents(EvmContractEventsTemplate { contracts: vec!["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".to_string()], - event_hashes: Some(vec![ + event_hashes: vec![ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_string(), - ]), + ], }); let contract_events_params = CreateWebhookFromTemplateParams { name: "E2E Test Webhook (evmContractEvents)".to_string(), @@ -113,7 +113,7 @@ async fn main() { destination_attributes: WebhookDestinationAttributes { url: "https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args: contract_events_args, }; diff --git a/crates/core/src/webhooks/mod.rs b/crates/core/src/webhooks/mod.rs index 225fb79..3043f41 100644 --- a/crates/core/src/webhooks/mod.rs +++ b/crates/core/src/webhooks/mod.rs @@ -255,8 +255,8 @@ impl WebhooksApiClient { /// Creates a new webhook from a predefined filter template. Requires a /// descriptive name, a target blockchain network, and destination - /// attributes (URL, optional security token — auto-generated when omitted, - /// and optional compression — `gzip` or `none`). `template_args` carries + /// attributes (URL, compression — `gzip` or `none`, and an optional + /// security token — auto-generated when omitted). `template_args` carries /// template-specific configuration such as wallet addresses or contract /// filters. An optional `notification_email` receives alerts if the /// webhook terminates. @@ -643,7 +643,7 @@ mod tests { destination_attributes: WebhookDestinationAttributes { url: "https://example.com/hook".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args, }; @@ -674,7 +674,7 @@ mod tests { destination_attributes: WebhookDestinationAttributes { url: "https://example.com/hook".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args, }; @@ -760,7 +760,7 @@ mod tests { let sdk = make_sdk(format!("{}/", server.uri())); let template_args = TemplateArgs::EvmContractEvents(EvmContractEventsTemplate { contracts: vec!["0xa0b8".to_string()], - event_hashes: Some(vec!["0xabcd".to_string()]), + event_hashes: vec!["0xabcd".to_string()], }); let params = CreateWebhookFromTemplateParams { name: "test-webhook".to_string(), @@ -769,7 +769,7 @@ mod tests { destination_attributes: WebhookDestinationAttributes { url: "https://example.com/hook".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args, }; diff --git a/crates/core/src/webhooks/webhook.rs b/crates/core/src/webhooks/webhook.rs index 89cad36..0319432 100644 --- a/crates/core/src/webhooks/webhook.rs +++ b/crates/core/src/webhooks/webhook.rs @@ -92,8 +92,8 @@ impl EvmWalletFilterTemplate { } } -/// Template arguments for filtering EVM contract events, optionally scoped to -/// a specific set of event topic hashes. +/// Template arguments for filtering EVM contract events, scoped to a specific +/// set of event topic hashes. #[cfg_attr(feature = "rust", derive(Builder))] #[cfg_attr(feature = "python", gen_stub_pyclass)] #[cfg_attr(feature = "python", pyclass(get_all, set_all))] @@ -103,9 +103,8 @@ impl EvmWalletFilterTemplate { pub struct EvmContractEventsTemplate { /// Contract addresses to watch for events. pub contracts: Vec, - /// Optional list of event topic hashes to restrict the filter to specific events. - #[serde(skip_serializing_if = "Option::is_none")] - pub event_hashes: Option>, + /// Event topic hashes to restrict the filter to specific events. + pub event_hashes: Vec, } #[cfg(feature = "python")] @@ -113,8 +112,7 @@ pub struct EvmContractEventsTemplate { #[pymethods] impl EvmContractEventsTemplate { #[new] - #[pyo3(signature = (contracts, event_hashes=None))] - pub fn new(contracts: Vec, event_hashes: Option>) -> Self { + pub fn new(contracts: Vec, event_hashes: Vec) -> Self { Self { contracts, event_hashes, @@ -316,9 +314,8 @@ pub struct WebhookDestinationAttributes { /// Optional token sent with each payload so the receiver can verify authenticity; generated automatically when omitted. #[serde(skip_serializing_if = "Option::is_none")] pub security_token: Option, - /// Optional payload compression (`gzip` or `none`). - #[serde(skip_serializing_if = "Option::is_none")] - pub compression: Option, + /// Payload compression (`gzip` or `none`). + pub compression: String, } #[cfg(feature = "python")] @@ -326,8 +323,8 @@ pub struct WebhookDestinationAttributes { #[pymethods] impl WebhookDestinationAttributes { #[new] - #[pyo3(signature = (url, security_token=None, compression=None))] - pub fn new(url: String, security_token: Option, compression: Option) -> Self { + #[pyo3(signature = (url, compression, security_token=None))] + pub fn new(url: String, compression: String, security_token: Option) -> Self { Self { url, security_token, @@ -544,7 +541,7 @@ mod template_args_tests { fn evm_contract_events_roundtrip() { let args = TemplateArgs::EvmContractEvents(EvmContractEventsTemplate { contracts: vec!["0xdef".to_string()], - event_hashes: Some(vec!["0x1234".to_string()]), + event_hashes: vec!["0x1234".to_string()], }); let json = serde_json::to_string(&args).unwrap(); assert!(json.contains(r#""templateId":"evmContractEvents""#)); @@ -640,7 +637,7 @@ mod template_args_tests { destination_attributes: WebhookDestinationAttributes { url: "https://x".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args: TemplateArgs::EvmWalletFilter(EvmWalletFilterTemplate { wallets: vec!["0xabc".to_string()], diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index efbe26f..855ff51 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -1201,8 +1201,8 @@ impl WebhooksApiClient { /// Creates a new webhook from a predefined filter template. Requires a /// descriptive name, a target blockchain network, and destination - /// attributes (URL, optional security token — auto-generated when omitted, - /// and optional compression — `gzip` or `none`). `template_args` carries + /// attributes (URL, compression — `gzip` or `none`, and an optional + /// security token — auto-generated when omitted). `template_args` carries /// template-specific configuration such as wallet addresses or contract /// filters. An optional `notification_email` receives alerts if the /// webhook terminates. diff --git a/crates/node/src/webhooks_template.rs b/crates/node/src/webhooks_template.rs index e06e4f6..af0e5b1 100644 --- a/crates/node/src/webhooks_template.rs +++ b/crates/node/src/webhooks_template.rs @@ -104,14 +104,9 @@ mod tests { unreachable!("expected EvmContractEvents variant") }; assert_eq!( - t.event_hashes.as_deref(), - Some( - [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - .to_string() - ] - .as_slice() - ), + t.event_hashes.as_slice(), + ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_string()] + .as_slice(), ); let params = core::webhooks::CreateWebhookFromTemplateParams { @@ -121,7 +116,7 @@ mod tests { destination_attributes: core::webhooks::WebhookDestinationAttributes { url: "https://x".to_string(), security_token: None, - compression: None, + compression: "none".to_string(), }, template_args: parsed, }; diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 2e41888..2cb347a 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -2035,8 +2035,8 @@ impl WebhooksApiClient { /// Creates a new webhook from a predefined filter template. Requires a /// descriptive name, a target blockchain network, and destination - /// attributes (URL, optional security token — auto-generated when omitted, - /// and optional compression — `gzip` or `none`). `template_args` carries + /// attributes (URL, compression — `gzip` or `none`, and an optional + /// security token — auto-generated when omitted). `template_args` carries /// template-specific configuration such as wallet addresses or contract /// filters. An optional `notification_email` receives alerts if the /// webhook terminates. diff --git a/npm/README.md b/npm/README.md index 5c85e1a..301834a 100644 --- a/npm/README.md +++ b/npm/README.md @@ -1262,7 +1262,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | Factory | Argument struct | Fields | |---|---|---| | `evm_wallet_filter` | `EvmWalletFilterTemplate` | `wallets: string[]` | -| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `event_hashes?: string[]` | +| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `event_hashes: string[]` | | `evm_abi_filter` | `EvmAbiFilterTemplate` | `abi: string` (JSON), `contracts: string[]` | | `solana_wallet_filter` | `SolanaWalletFilterTemplate` | `accounts: string[]` | | `bitcoin_wallet_filter` | `BitcoinWalletFilterTemplate` | `wallets: string[]` | @@ -1270,7 +1270,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | `hyperliquid_wallet_events_filter` | `HyperliquidWalletEventsFilterTemplate` | `wallets: string[]` | | `stellar_wallet_transactions_filter` | `StellarWalletTransactionsFilterTemplate` | `source_accounts: string[]` | -`WebhookDestinationAttributes`: `url` (required), `security_token` (optional — auto-generated if omitted), `compression` (optional — `"none"` | `"gzip"`). +`WebhookDestinationAttributes`: `url` (required), `compression` (required — `"none"` | `"gzip"`), `security_token` (optional — auto-generated if omitted). `WebhookStartFrom`: `Last` (resume from last delivered block) or `Latest` (start from newest). @@ -1319,7 +1319,7 @@ import { TemplateArgs } from "quicknode-sdk"; const webhook = await qn.webhooks.createWebhookFromTemplate({ name: "Wallet Webhook", network: "ethereum-mainnet", - destinationAttributes: { url: "https://webhook.site/..." }, + destinationAttributes: { url: "https://webhook.site/...", compression: "none" }, templateArgs: TemplateArgs.evmWalletFilter({ wallets: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"], }), diff --git a/npm/examples/webhooks_e2e.ts b/npm/examples/webhooks_e2e.ts index cd140e6..28f4a67 100644 --- a/npm/examples/webhooks_e2e.ts +++ b/npm/examples/webhooks_e2e.ts @@ -20,6 +20,7 @@ async function main() { network: "ethereum-mainnet", destinationAttributes: { url: "https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef", + compression: "none", }, templateArgs: TemplateArgs.evmWalletFilter({ wallets: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"], @@ -56,6 +57,7 @@ async function main() { network: "ethereum-mainnet", destinationAttributes: { url: "https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef", + compression: "none", }, templateArgs: TemplateArgs.evmContractEvents({ contracts: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"], diff --git a/npm/index.d.ts b/npm/index.d.ts index 031f424..bd4f523 100644 --- a/npm/index.d.ts +++ b/npm/index.d.ts @@ -611,14 +611,14 @@ export interface EvmAbiFilterTemplate { } /** - * Template arguments for filtering EVM contract events, optionally scoped to - * a specific set of event topic hashes. + * Template arguments for filtering EVM contract events, scoped to a specific + * set of event topic hashes. */ export interface EvmContractEventsTemplate { /** Contract addresses to watch for events. */ contracts: Array - /** Optional list of event topic hashes to restrict the filter to specific events. */ - eventHashes?: Array + /** Event topic hashes to restrict the filter to specific events. */ + eventHashes: Array } /** @@ -1800,8 +1800,8 @@ export interface WebhookDestinationAttributes { url: string /** Optional token sent with each payload so the receiver can verify authenticity; generated automatically when omitted. */ securityToken?: string - /** Optional payload compression (`gzip` or `none`). */ - compression?: string + /** Payload compression (`gzip` or `none`). */ + compression: string } /** Response from `get_enabled_count` for webhooks. */ @@ -2331,8 +2331,8 @@ export declare class WebhooksApiClient { /** * Creates a new webhook from a predefined filter template. Requires a * descriptive name, a target blockchain network, and destination - * attributes (URL, optional security token — auto-generated when omitted, - * and optional compression — `gzip` or `none`). `template_args` carries + * attributes (URL, compression — `gzip` or `none`, and an optional + * security token — auto-generated when omitted). `template_args` carries * template-specific configuration such as wallet addresses or contract * filters. An optional `notification_email` receives alerts if the * webhook terminates. diff --git a/python/README.md b/python/README.md index 093a79f..9ae75f2 100644 --- a/python/README.md +++ b/python/README.md @@ -1257,7 +1257,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | Factory | Argument struct | Fields | |---|---|---| | `evm_wallet_filter` | `EvmWalletFilterTemplate` | `wallets: string[]` | -| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `event_hashes?: string[]` | +| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `event_hashes: string[]` | | `evm_abi_filter` | `EvmAbiFilterTemplate` | `abi: string` (JSON), `contracts: string[]` | | `solana_wallet_filter` | `SolanaWalletFilterTemplate` | `accounts: string[]` | | `bitcoin_wallet_filter` | `BitcoinWalletFilterTemplate` | `wallets: string[]` | @@ -1265,7 +1265,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | `hyperliquid_wallet_events_filter` | `HyperliquidWalletEventsFilterTemplate` | `wallets: string[]` | | `stellar_wallet_transactions_filter` | `StellarWalletTransactionsFilterTemplate` | `source_accounts: string[]` | -`WebhookDestinationAttributes`: `url` (required), `security_token` (optional — auto-generated if omitted), `compression` (optional — `"none"` | `"gzip"`). +`WebhookDestinationAttributes`: `url` (required), `compression` (required — `"none"` | `"gzip"`), `security_token` (optional — auto-generated if omitted). `WebhookStartFrom`: `Last` (resume from last delivered block) or `Latest` (start from newest). @@ -1314,7 +1314,10 @@ from quicknode_sdk import EvmWalletFilterArgs, EvmWalletFilterTemplate, WebhookD webhook = await qn.webhooks.create_webhook_from_template( name="Wallet Webhook", network="ethereum-mainnet", - destination_attributes=WebhookDestinationAttributes(url="https://webhook.site/..."), + destination_attributes=WebhookDestinationAttributes( + url="https://webhook.site/...", + compression="none", + ), template_args=EvmWalletFilterArgs( EvmWalletFilterTemplate(wallets=["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"]) ), diff --git a/python/examples/webhooks_e2e.py b/python/examples/webhooks_e2e.py index a404488..1d64815 100644 --- a/python/examples/webhooks_e2e.py +++ b/python/examples/webhooks_e2e.py @@ -24,6 +24,7 @@ async def main(): network="ethereum-mainnet", destination_attributes=WebhookDestinationAttributes( url="https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef", + compression="none", ), template_args=EvmWalletFilterArgs( EvmWalletFilterTemplate(wallets=["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"]) @@ -54,6 +55,7 @@ async def main(): network="ethereum-mainnet", destination_attributes=WebhookDestinationAttributes( url="https://webhook.site/ae19071a-2dcc-4035-9cdf-406dcb4719ef", + compression="none", ), template_args=EvmContractEventsArgs( EvmContractEventsTemplate( diff --git a/python/quicknode_sdk/_core/__init__.pyi b/python/quicknode_sdk/_core/__init__.pyi index f7c6fc7..2b856a3 100644 --- a/python/quicknode_sdk/_core/__init__.pyi +++ b/python/quicknode_sdk/_core/__init__.pyi @@ -2543,8 +2543,8 @@ class EvmContractEventsArgs: @typing.final class EvmContractEventsTemplate: r""" - Template arguments for filtering EVM contract events, optionally scoped to - a specific set of event topic hashes. + Template arguments for filtering EVM contract events, scoped to a specific + set of event topic hashes. """ @property def contracts(self) -> builtins.list[builtins.str]: @@ -2557,16 +2557,16 @@ class EvmContractEventsTemplate: Contract addresses to watch for events. """ @property - def event_hashes(self) -> typing.Optional[builtins.list[builtins.str]]: + def event_hashes(self) -> builtins.list[builtins.str]: r""" - Optional list of event topic hashes to restrict the filter to specific events. + Event topic hashes to restrict the filter to specific events. """ @event_hashes.setter - def event_hashes(self, value: typing.Optional[builtins.list[builtins.str]]) -> None: + def event_hashes(self, value: builtins.list[builtins.str]) -> None: r""" - Optional list of event topic hashes to restrict the filter to specific events. + Event topic hashes to restrict the filter to specific events. """ - def __new__(cls, contracts: typing.Sequence[builtins.str], event_hashes: typing.Optional[typing.Sequence[builtins.str]] = None) -> EvmContractEventsTemplate: ... + def __new__(cls, contracts: typing.Sequence[builtins.str], event_hashes: typing.Sequence[builtins.str]) -> EvmContractEventsTemplate: ... @typing.final class EvmWalletFilterArgs: @@ -6505,16 +6505,16 @@ class WebhookDestinationAttributes: Optional token sent with each payload so the receiver can verify authenticity; generated automatically when omitted. """ @property - def compression(self) -> typing.Optional[builtins.str]: + def compression(self) -> builtins.str: r""" - Optional payload compression (`gzip` or `none`). + Payload compression (`gzip` or `none`). """ @compression.setter - def compression(self, value: typing.Optional[builtins.str]) -> None: + def compression(self, value: builtins.str) -> None: r""" - Optional payload compression (`gzip` or `none`). + Payload compression (`gzip` or `none`). """ - def __new__(cls, url: builtins.str, security_token: typing.Optional[builtins.str] = None, compression: typing.Optional[builtins.str] = None) -> WebhookDestinationAttributes: ... + def __new__(cls, url: builtins.str, compression: builtins.str, security_token: typing.Optional[builtins.str] = None) -> WebhookDestinationAttributes: ... @typing.final class WebhookEnabledCountResponse: @@ -6624,8 +6624,8 @@ class WebhooksApiClient: r""" Creates a new webhook from a predefined filter template. Requires a descriptive name, a target blockchain network, and destination - attributes (URL, optional security token — auto-generated when omitted, - and optional compression — `gzip` or `none`). `template_args` carries + attributes (URL, compression — `gzip` or `none`, and an optional + security token — auto-generated when omitted). `template_args` carries template-specific configuration such as wallet addresses or contract filters. An optional `notification_email` receives alerts if the webhook terminates. diff --git a/ruby/README.md b/ruby/README.md index 91b9b87..f543e83 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -1268,7 +1268,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | Factory | Argument struct | Fields | |---|---|---| | `evm_wallet_filter` | `EvmWalletFilterTemplate` | `wallets: string[]` | -| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `eventHashes?: string[]` (camelCase — `event_hashes` is rejected by the API) | +| `evm_contract_events` | `EvmContractEventsTemplate` | `contracts: string[]`, `eventHashes: string[]` (camelCase — `event_hashes` is rejected by the API) | | `evm_abi_filter` | `EvmAbiFilterTemplate` | `abi: string` (JSON), `contracts: string[]` | | `solana_wallet_filter` | `SolanaWalletFilterTemplate` | `accounts: string[]` | | `bitcoin_wallet_filter` | `BitcoinWalletFilterTemplate` | `wallets: string[]` | @@ -1276,7 +1276,7 @@ Accessed as `qn.webhooks`. Creates webhooks from filter templates and manages th | `hyperliquid_wallet_events_filter` | `HyperliquidWalletEventsFilterTemplate` | `wallets: string[]` | | `stellar_wallet_transactions_filter` | `StellarWalletTransactionsFilterTemplate` | `source_accounts: string[]` | -`WebhookDestinationAttributes`: `url` (required), `security_token` (optional — auto-generated if omitted), `compression` (optional — `"none"` | `"gzip"`). +`WebhookDestinationAttributes`: `url` (required), `compression` (required — `"none"` | `"gzip"`), `security_token` (optional — auto-generated if omitted). `WebhookStartFrom`: `Last` (resume from last delivered block) or `Latest` (start from newest). From 770176f8c0f99d5519c2fee882add98d78735481 Mon Sep 17 00:00:00 2001 From: John Mitsch Date: Tue, 2 Jun 2026 12:09:21 -0400 Subject: [PATCH 2/2] feat(webhooks): add ByList template variants for list-reference filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every webhook template supports two input shapes on the API: inline values (e.g. `wallets: [...]`) or a reference to a pre-created list by name (e.g. `walletsListName: "my_wallets"`). The SDK previously only modeled the inline shape, so callers had no compile-safe way to use the ByList form even though the API has supported it on every template. Each template now has a paired `*ByListTemplate` struct and the existing `TemplateArgs` variants hold a per-template `