Skip to content

fix(node): align webhook store with the cross-SDK contract - #494

Merged
kartikeya-27 merged 5 commits into
ByteVeda:masterfrom
stromanni:fix/node-webhook-store-contract
Jul 22, 2026
Merged

fix(node): align webhook store with the cross-SDK contract#494
kartikeya-27 merged 5 commits into
ByteVeda:masterfrom
stromanni:fix/node-webhook-store-contract

Conversation

@stromanni

@stromanni stromanni commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Closes #482.

The Node SDK persisted webhook subscriptions as one camelCase setting per hook (webhook:<id>), while the cross-SDK contract is a single snake_case JSON array under webhooks:subscriptions. Hooks registered by one runtime were invisible to another against the same database. Delivery logs (webhooks:deliveries:<id>) already agreed, and the Node dashboard HTTP mapper already emitted the contract shape — the divergence was at exactly one layer.

Changes

  • webhooks/store.ts is now a codec over the canonical key: timeoutMs <-> timeout_seconds, taskFilter <-> task_filter, and so on. Fields it does not model are carried through on rewrite, since every mutation rewrites the whole array. Unknown event names are kept verbatim.
  • Legacy webhook:<id> records fold into the list on first read and are then deleted; a canonical row wins on id collision.
  • Added retryBackoff to Webhook/WebhookInput, honored in the deliverer as base ** (attempt - 1) seconds. This un-hardcodes retry_backoff: 2.0 in the dashboard contract; the first retry moves from 500ms to 1s to match the contract curve.
  • Layout pinned in crates/taskito-core/BINDING_CONTRACT.md; Node webhook docs updated.

Verification

  • 8 new tests in test/core/webhookStoreContract.test.ts; full Node suite green (74 files / 459 tests); biome and tsc clean.
  • Checked end-to-end against a single SQLite file in both directions: a subscription written by another runtime reads back with every field intact, and the row this SDK writes reads there as timeout_seconds=7.0, retry_backoff=4.0.

Not included

The Java store is a third layout (key taskito.webhooks, camelCase, taskFilter a scalar String). Aligning it changes a public field type, so it belongs in a separate change.

Summary by CodeRabbit

  • New Features

    • Added configurable webhook retry backoff, per-attempt timeout, and maximum retries, with the SPA and delivery logic honoring the provided base backoff.
    • Webhook subscriptions are now stored in a shared, contract-based format so subscriptions created in one runtime appear across SDKs.
  • Bug Fixes

    • Improved robustness when stored webhook data is missing, corrupted, or partially invalid; ignores bad rows safely.
    • Added migration from legacy per-webhook storage to the shared subscriptions layout without overwriting canonical entries.
  • Documentation

    • Updated the webhooks extensibility guide with the new retry-related configuration options and cross-SDK persistence details.
  • Tests

    • Added contract tests covering cross-SDK reads/writes, migration behavior, and preservation of extra/unmodeled fields.

The contract carries retry_backoff per subscription; the dashboard was reporting a constant.
Subscriptions moved from one setting per hook to the shared webhooks:subscriptions list, so hooks registered by another SDK are no longer invisible. Legacy records migrate on first read.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 184c5546-de2c-4b36-978f-245d2c41fb16

📥 Commits

Reviewing files that changed from the base of the PR and between ed9ffdb and e64820a.

📒 Files selected for processing (2)
  • sdks/node/src/webhooks/store.ts
  • sdks/node/src/webhooks/types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • sdks/node/src/webhooks/types.ts
  • sdks/node/src/webhooks/store.ts

📝 Walkthrough

Walkthrough

Aligns Node webhook persistence with the cross-SDK webhooks:subscriptions contract, adds legacy migration and lossless rewrites, and introduces configurable retry backoff across creation, dashboard handling, and delivery.

Changes

Webhook contract and persistence

Layer / File(s) Summary
Retry configuration flow
sdks/node/src/webhooks/types.ts, sdks/node/src/webhooks/manager.ts, sdks/node/src/webhooks/deliverer.ts, sdks/node/src/dashboard/handlers/*, docs/content/docs/node/guides/extensibility/webhooks.mdx
Adds retryBackoff to webhook models, validates and defaults it, propagates it through dashboard handlers, and uses it for capped exponential delivery delays.
Canonical subscription storage
crates/taskito-core/BINDING_CONTRACT.md, sdks/node/src/webhooks/store.ts, docs/content/docs/node/guides/extensibility/webhooks.mdx
Stores subscriptions as a snake_case JSON array under webhooks:subscriptions, defensively decodes rows, and preserves unmodelled fields during rewrites.
Legacy migration and contract validation
sdks/node/src/webhooks/store.ts, sdks/node/test/core/webhookStoreContract.test.ts
Migrates legacy webhook:<id> records, prefers canonical rows on collisions, removes migrated keys, and tests persistence, compatibility, resilience, ordering, updates, and deletion.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • ByteVeda/taskito#490: Modifies the same webhook delivery loop with per-attempt URL safety validation.
  • ByteVeda/taskito#491: Modifies the same webhook retry and delivery flow with SSRF safety handling.

Suggested labels: storage

Suggested reviewers: kartikeya-27

Sequence Diagram(s)

sequenceDiagram
  participant DashboardHandler
  participant WebhookManager
  participant WebhookStore
  participant SettingsKV
  DashboardHandler->>WebhookManager: parse retry_backoff and create webhook
  WebhookManager->>WebhookStore: persist validated subscription
  WebhookStore->>SettingsKV: rewrite webhooks:subscriptions JSON array
  SettingsKV-->>WebhookStore: store canonical subscription list
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly describes the main change to the Node webhook store contract.
Linked Issues check ✅ Passed The Node webhook store now uses webhooks:subscriptions, migrates legacy webhook:<id> rows, and preserves read compatibility as required by #482.
Out of Scope Changes check ✅ Passed The retryBackoff and documentation updates align with the stated PR objectives and do not appear unrelated to the webhook contract work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pratyush618
pratyush618 requested a review from kartikeya-27 July 21, 2026 18:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
sdks/node/src/webhooks/store.ts (1)

16-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Default constants duplicated between manager.ts and store.ts.

DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, and the new DEFAULT_RETRY_BACKOFF are declared identically here and in sdks/node/src/webhooks/manager.ts (lines 15-17). One is used for creation-time defaults, the other for decode-time fallbacks on malformed/legacy rows — but since the values must stay in sync for consistent behavior, duplicating them risks silent drift if one file is updated without the other.

♻️ Suggested consolidation
// sdks/node/src/webhooks/defaults.ts
export const DEFAULT_MAX_RETRIES = 3;
export const DEFAULT_TIMEOUT_MS = 10_000;
export const DEFAULT_RETRY_BACKOFF = 2;

Then import this module from both manager.ts and store.ts instead of redeclaring.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/node/src/webhooks/store.ts` around lines 16 - 18, Move
DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, and DEFAULT_RETRY_BACKOFF into a shared
webhooks defaults module, then update manager.ts and store.ts to import these
constants instead of declaring local copies. Preserve the existing creation-time
defaults and decode-time fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdks/node/src/webhooks/store.ts`:
- Around line 256-269: Update the migration logic around the legacyKeys loop to
track the number of successfully decoded, newly merged webhooks, and use that
count in the migration log instead of legacyKeys.length. Preserve deduplication
against known canonical IDs, while retaining the existing cleanup of legacy
keys.
- Around line 171-241: The put and delete mutations in the webhook store can
race because load and save are separate operations. Serialize the entire
read-modify-write sequence in put and delete using a backend lock, or add a
compare-and-swap/version check around SUBSCRIPTIONS_KEY, so concurrent writers
cannot overwrite each other’s webhook changes while preserving the existing
update and deletion behavior.

In `@sdks/node/src/webhooks/types.ts`:
- Around line 18-19: Update both retryBackoff comments in the webhook type
definitions to document the implemented formula using zero-based wait numbering:
the Nth wait uses retryBackoff ** N, matching deliverer.ts backoff() and the
wording in webhooks.mdx.

---

Nitpick comments:
In `@sdks/node/src/webhooks/store.ts`:
- Around line 16-18: Move DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, and
DEFAULT_RETRY_BACKOFF into a shared webhooks defaults module, then update
manager.ts and store.ts to import these constants instead of declaring local
copies. Preserve the existing creation-time defaults and decode-time fallback
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59538052-6d9b-4ab2-92fb-7ecb67a47bc9

📥 Commits

Reviewing files that changed from the base of the PR and between e41f08d and ed9ffdb.

📒 Files selected for processing (9)
  • crates/taskito-core/BINDING_CONTRACT.md
  • docs/content/docs/node/guides/extensibility/webhooks.mdx
  • sdks/node/src/dashboard/handlers/contract.ts
  • sdks/node/src/dashboard/handlers/core.ts
  • sdks/node/src/webhooks/deliverer.ts
  • sdks/node/src/webhooks/manager.ts
  • sdks/node/src/webhooks/store.ts
  • sdks/node/src/webhooks/types.ts
  • sdks/node/test/core/webhookStoreContract.test.ts

Comment thread sdks/node/src/webhooks/store.ts
Comment thread sdks/node/src/webhooks/store.ts Outdated
Comment thread sdks/node/src/webhooks/types.ts Outdated
Undecodable or id-colliding legacy records are retired, not merged; count them out of the log.
@kartikeya-27
kartikeya-27 merged commit bc8e14c into ByteVeda:master Jul 22, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align the Node webhook store key shape with the cross-SDK contract

2 participants