fix(webhooks)!: mark compression and event_hashes required#39
Merged
Conversation
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.
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 `<Template>Input` enum that picks between inline and ByList. The two shapes share a `templateId` and URL path; the server disambiguates by field presence. Untagged serde dispatch on disjoint field names handles the wire encoding. Resolution rules verified against the live API (audit script run against api.quicknode.com/webhooks/rest/v1): * `EvmContractEventsByList.event_hashes_list_name` — optional; server accepts contracts-only and matches all events from those contracts. Mirrors the asymmetric behavior between inline (both required) and ByList (one required, one optional). * `EvmAbiFilterByList.contracts_list_name` — optional per both OpenAPI and live behavior; the ABI alone is a valid filter. * `EvmAbiFilterByList` uses `abiJson` on the wire, distinct from the inline variant's `abi`. Polyglot ripple: Python wrappers gain matching `*ByListArgs` classes (macro-generated), Node TS factory methods now accept either inline or ByList template, READMEs document both forms per template. Ruby webhook methods take JSON pass-through so no Ruby code change. The core enum names are `<Template>Input` (not `<Template>Args`) to avoid colliding with the user-facing `<Template>Args` Python wrapper class names.
AlejandroFabianCampos
approved these changes
Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two commits resolving SDK drift from the live webhooks REST API.
Commit 1: required-field fix
The webhooks REST API requires
compressionon every webhook destination andeventHasheson everyevmContractEventstemplate, but both fields were typedOptionin the SDK. Calls that omitted them failed at runtime (HTTP 400 for compression, HTTP 500 for event hashes). This tightens the types so the requirement surfaces at the type system instead of at the network boundary.Commit 2: ByList template variants
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. This commit adds 8*ByListTemplatestructs and anInline | ByListenum per template, with serde untagged dispatch on disjoint field names.The two forms share a
templateIdand URL path; the server disambiguates by field presence.Live-verified drift map
Verified by running raw curls against
https://api.quicknode.com/webhooks/rest/v1/and cross-checking the published OpenAPI spec.Required-field drift (commit 1)
WebhookDestinationAttributes.compressionOption<String>EvmContractEventsTemplate.event_hashesOption<Vec<String>>WebhookDestinationAttributes.security_tokenOption<String>EvmAbiFilterTemplate.contractsByList behavior (commit 2)
Every template accepts a parallel
*ByListshape. Two have asymmetric inline-vs-ByList behavior:EvmContractEventscontractsANDeventHashesrequiredcontractsListNamerequired,eventHashesListNameoptionalEvmAbiFilterabirequired,contractsrequired (per live)abiJsonrequired,contractsListNameoptional (per spec AND live)The ByList variant of EVMABI also uses a different wire key (
abiJsonvsabi).Polyglot ripple
<Template>Inputenums (Inline | ByList).#[pyclass]wrappers (*Argsfor inline,*ByListArgsfor by-list) via macro-extendedtemplate_wrapper!. Added module registrations and__init__.pyiexports.TemplateArgsInputaccepts either inline or ByList;TemplateArgsfactory methods now accept the union.template_args_jsoncontinues to work with either shape.OpenAPI spec issues to report to the API team
WebhookAttributes.security_tokenis marked required but the server auto-generates it when absent. Therequiredarray should not include it.EVMABIFilterTemplate.contractsis marked optional but the server rejects calls that omit it. Therequiredarray should be["abi", "contracts"], not["abi"].EVMContractEventsTemplatehas norequiredarray declared, but the server requires bothcontractsandeventHashes. Therequiredarray should be["contracts", "eventHashes"].Polish (not blocking the SDK fix):
4. Missing required template args return HTTP 500 (
Expected array for arg "...") instead of a clean 400.5. The original compression error rendered the value as a literal empty string (
invalid compression: ""), suggesting absent-to-empty-string coercion before validation. A clean "compression is required" 400 would be friendlier to non-SDK callers.https://linear.app/quicknode/issue/BLD-1324/webhooks-rest-api-openapi-spec-drift-and-error-response-polish
Test plan
cargo checkjust lintjust test(211 core tests, all pass)just python-buildjust node-buildjust ruby-buildcargo test -p sdk-node --lib(existingevm_contract_events_preserves_event_hashes_through_outbound_wiretest still passes)cargo run --example webhooks_e2e -p quicknode-sdkagainst a live API keypython/examples/webhooks_e2e.pyagainst a live API keynpm/examples/webhooks_e2e.tsagainst a live API keyruby/examples/webhooks_e2e.rbagainst a live API keyCloses DX-5345.