You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
README.md documents five pool.* webhook event types as real, subscribable events — with a worked example showing a client registering a webhook for exactly two of them — but no code path anywhere in this repository ever dispatches any pool.* event. Every webhook subscribed to a pool.* event type is permanently, silently dead: it will never receive a delivery, and nothing tells the subscriber that.
README.md documents:
| `pool.created` | A new farming pool was created on-chain |
| `pool.assets_locked` | Assets were locked into a pool |
| `pool.assets_unlocked` | Assets were unlocked from a pool |
| `pool.rewards_distributed` | Pool distributed rewards to participants |
| `pool.closed` | Pool was closed |
and gives this exact worked example for POST /webhooks:
and documents the test endpoint as sending "a synthetic pool.assets_locked payload."
But webhookDispatcher.dispatch() — the only function anywhere in this codebase that actually delivers a webhook for a real event — is called from exactly one call site in the entire src/ tree:
...and that single call site dispatches only event_type: 'airdrop.failed'. No route, no job, and no part of the indexer ever calls dispatch() with any pool.* event type. Confirming further: src/indexer/eventParser.js's EVENT_FIELDS — the exhaustive list of on-chain contract events this indexer knows how to decode at all — only defines airdrop_created, recipient_added, token_claimed, airdrop_expired. There is no pool_created/pool_locked/etc. parsing logic anywhere in the current indexer; per its single commit message ("add soroban event indexer"), the indexer as it exists today was built exclusively around airdrop lifecycle events, even though historical (closed) issue #1 was originally titled "Create Event Indexer for Pool Lock/Unlock/Boost Events" — the indexer appears to have fully pivoted to airdrop-only events at some point, without webhookEvents.js's POOL_EVENTS registration or the README's documentation being updated to match.
This means: a real integrator, following the README's own worked example exactly (events: ["pool.assets_locked", "pool.rewards_distributed"]), registers a webhook that webhookCreateBodySchema's validation (webhookEvents.isValidSubscription) happily accepts as valid — because pool.assets_locked and pool.rewards_distributedare registered, known event names in webhookEvents.js's POOL_EVENTS — and then never receives a single delivery for it, ever, with the webhook sitting there indefinitely reporting no deliveries and no errors (there's nothing to error on, since nothing ever tries to deliver to it), giving no signal distinguishing "correctly configured, just no matching events have happened yet" from "this event type doesn't actually exist in this deployment."
Requirements
Update README.md's webhook events documentation to accurately reflect which event types have a real, working dispatch path today (currently: only airdrop.failed) — either remove the pool.* events from the documented/example payloads entirely, or clearly mark them as "planned, not yet implemented."
Either wire up real pool.* event dispatch (requires extending the indexer to parse pool-related on-chain events, which per the above does not currently exist at all, and adding the corresponding dispatch() calls) as a genuine feature, or remove POOL_EVENTS from webhookEvents.js's registered/subscribable event list until that work exists, so webhookCreateBodySchema validation doesn't accept subscriptions to event types that can never fire.
If keeping POOL_EVENTS registered as a forward-looking placeholder is intentional, at minimum follow the pattern already established for AIRDROP_EVENTS in the same file — that constant's comment explicitly says "Only 'airdrop.failed' is registered here... nothing in the codebase dispatches [the others] yet" — extend an equivalent, equally explicit comment to POOL_EVENTS, since today it has no such caveat at all and reads as fully live.
Update POST /webhooks/:id/test's documentation/behavior consideration: it sends a synthetic pool.assets_locked test payload specifically, which — given pool.assets_locked is otherwise entirely undeliverable — means the only way a subscriber to pool.assets_locked will ever see any payload from this event type is the synthetic test one, which could itself be misleading (a successful test delivery reasonably implies "this webhook is correctly wired up and will receive real events," which is not true here).
Acceptance Criteria
README's webhook events table and worked examples accurately reflect real, currently-dispatchable event types, or clearly flag which are not yet implemented.
webhookEvents.js's POOL_EVENTS either has dispatch support wired up (indexer parsing + dispatch() calls), or is removed from isValidSubscription's accepted set (with a migration/communication plan for any already-registered subscriptions, if this is a live production API), or carries the same explicit "not yet wired up" caveat comment AIRDROP_EVENTS already has for its own not-yet-implemented members.
The webhook test-endpoint documentation is updated to clarify that a successful test delivery does not imply the subscribed event type will ever fire for real, for any event type without a live dispatch path.
Additional Notes
More precise references
README.md:453-457,470,490,506,513: confirmed the pool.* events table, the worked POST /webhooks example subscribing to pool.assets_locked/pool.rewards_distributed, the test-endpoint description referencing a synthetic pool.assets_locked payload, and a sample delivery payload using pool.assets_locked as the example event type.
src/services/webhookDispatcher.js:155-178 (dispatch): confirmed this is the sole function that delivers a webhook for a real (non-test) event, and confirmed via grep -rn "dispatcher.dispatch\|webhookDispatcher.dispatch" src/ that it is called from exactly one location.
src/jobs/airdropExpiry.js:70-79: confirmed the one and only real dispatch() call site, with event_type: 'airdrop.failed' hardcoded.
src/indexer/eventParser.js:4-9 (EVENT_FIELDS): confirmed the complete list of decodable event names is airdrop_created, recipient_added, token_claimed, airdrop_expired — no pool-related event name appears anywhere in this file.
src/services/webhookEvents.js:3-9 (POOL_EVENTS) vs :13-20 (AIRDROP_EVENTS, with its explicit "only airdrop.failed... registering unused event names here would let a client subscribe to something that can never fire" comment): confirmed POOL_EVENTS carries no equivalent caveat, despite being in the identical situation (registered, validated, acceptable at subscription time, but with zero working dispatch path).
src/services/webhookDispatcher.js:180-191 (sendTest): confirmed the hardcoded const eventType = 'pool.assets_locked'; used for all test deliveries regardless of what the webhook is actually subscribed to.
Historical issue Create Event Indexer for Pool Lock/Unlock/Boost Events #1 (closed) — "Create Event Indexer for Pool Lock/Unlock/Boost Events" — confirmed via its title that pool events were the indexer's original intended scope, providing context for why POOL_EVENTS exists in webhookEvents.js at all despite the indexer having since pivoted entirely to airdrop events.
Additional edge cases
If any webhooks have already been registered in a real deployment with pool.* subscriptions (impossible to verify from source alone, but plausible given the README explicitly walks a new integrator through doing exactly that), removing POOL_EVENTS from the valid-subscription set would need either a migration step (deactivating or flagging those existing webhooks) or, at minimum, a clear operational note — simply changing validation going forward wouldn't retroactively inform anyone who already configured a dead subscription based on the README's current instructions.
Worth checking whether openapi.yaml documents pool.* events the same way README.md does (I did not find pool. references in openapi.yaml during this review, suggesting the OpenAPI spec and the README may already disagree with each other independent of this issue — worth a quick audit while fixing this, since whichever document is treated as authoritative should be updated consistently).
Test/reproduction plan
Register a webhook via POST /webhooks with events: ["pool.assets_locked"] exactly per the README's own example; confirm webhookCreateBodySchema accepts it (it does today).
Search the codebase (as done above) for any call to dispatch() with a pool.* event type; confirm there are none.
After a fix, either confirm a real dispatch path exists and is tested end-to-end (indexer → dispatch() → delivery), or confirm the schema rejects pool.* subscriptions with a clear error, or confirm the README/code comments now accurately flag the gap.
Overview
README.mddocuments fivepool.*webhook event types as real, subscribable events — with a worked example showing a client registering a webhook for exactly two of them — but no code path anywhere in this repository ever dispatches anypool.*event. Every webhook subscribed to apool.*event type is permanently, silently dead: it will never receive a delivery, and nothing tells the subscriber that.README.md documents:
and gives this exact worked example for
POST /webhooks:{ "url": "...", "events": ["pool.assets_locked", "pool.rewards_distributed"], ... }and documents the test endpoint as sending "a synthetic
pool.assets_lockedpayload."But
webhookDispatcher.dispatch()— the only function anywhere in this codebase that actually delivers a webhook for a real event — is called from exactly one call site in the entiresrc/tree:...and that single call site dispatches only
event_type: 'airdrop.failed'. No route, no job, and no part of the indexer ever callsdispatch()with anypool.*event type. Confirming further:src/indexer/eventParser.js'sEVENT_FIELDS— the exhaustive list of on-chain contract events this indexer knows how to decode at all — only definesairdrop_created,recipient_added,token_claimed,airdrop_expired. There is nopool_created/pool_locked/etc. parsing logic anywhere in the current indexer; per its single commit message ("add soroban event indexer"), the indexer as it exists today was built exclusively around airdrop lifecycle events, even though historical (closed) issue #1 was originally titled "Create Event Indexer for Pool Lock/Unlock/Boost Events" — the indexer appears to have fully pivoted to airdrop-only events at some point, withoutwebhookEvents.js'sPOOL_EVENTSregistration or the README's documentation being updated to match.This means: a real integrator, following the README's own worked example exactly (
events: ["pool.assets_locked", "pool.rewards_distributed"]), registers a webhook thatwebhookCreateBodySchema's validation (webhookEvents.isValidSubscription) happily accepts as valid — becausepool.assets_lockedandpool.rewards_distributedare registered, known event names inwebhookEvents.js'sPOOL_EVENTS— and then never receives a single delivery for it, ever, with the webhook sitting there indefinitely reporting no deliveries and no errors (there's nothing to error on, since nothing ever tries to deliver to it), giving no signal distinguishing "correctly configured, just no matching events have happened yet" from "this event type doesn't actually exist in this deployment."Requirements
README.md's webhook events documentation to accurately reflect which event types have a real, working dispatch path today (currently: onlyairdrop.failed) — either remove thepool.*events from the documented/example payloads entirely, or clearly mark them as "planned, not yet implemented."pool.*event dispatch (requires extending the indexer to parse pool-related on-chain events, which per the above does not currently exist at all, and adding the correspondingdispatch()calls) as a genuine feature, or removePOOL_EVENTSfromwebhookEvents.js's registered/subscribable event list until that work exists, sowebhookCreateBodySchemavalidation doesn't accept subscriptions to event types that can never fire.POOL_EVENTSregistered as a forward-looking placeholder is intentional, at minimum follow the pattern already established forAIRDROP_EVENTSin the same file — that constant's comment explicitly says "Only 'airdrop.failed' is registered here... nothing in the codebase dispatches [the others] yet" — extend an equivalent, equally explicit comment toPOOL_EVENTS, since today it has no such caveat at all and reads as fully live.POST /webhooks/:id/test's documentation/behavior consideration: it sends a syntheticpool.assets_lockedtest payload specifically, which — givenpool.assets_lockedis otherwise entirely undeliverable — means the only way a subscriber topool.assets_lockedwill ever see any payload from this event type is the synthetic test one, which could itself be misleading (a successful test delivery reasonably implies "this webhook is correctly wired up and will receive real events," which is not true here).Acceptance Criteria
webhookEvents.js'sPOOL_EVENTSeither has dispatch support wired up (indexer parsing +dispatch()calls), or is removed fromisValidSubscription's accepted set (with a migration/communication plan for any already-registered subscriptions, if this is a live production API), or carries the same explicit "not yet wired up" caveat commentAIRDROP_EVENTSalready has for its own not-yet-implemented members.Additional Notes
More precise references
README.md:453-457,470,490,506,513: confirmed thepool.*events table, the workedPOST /webhooksexample subscribing topool.assets_locked/pool.rewards_distributed, the test-endpoint description referencing a syntheticpool.assets_lockedpayload, and a sample delivery payload usingpool.assets_lockedas the example event type.src/services/webhookDispatcher.js:155-178(dispatch): confirmed this is the sole function that delivers a webhook for a real (non-test) event, and confirmed viagrep -rn "dispatcher.dispatch\|webhookDispatcher.dispatch" src/that it is called from exactly one location.src/jobs/airdropExpiry.js:70-79: confirmed the one and only realdispatch()call site, withevent_type: 'airdrop.failed'hardcoded.src/indexer/eventParser.js:4-9(EVENT_FIELDS): confirmed the complete list of decodable event names isairdrop_created,recipient_added,token_claimed,airdrop_expired— no pool-related event name appears anywhere in this file.src/services/webhookEvents.js:3-9(POOL_EVENTS) vs:13-20(AIRDROP_EVENTS, with its explicit "only airdrop.failed... registering unused event names here would let a client subscribe to something that can never fire" comment): confirmedPOOL_EVENTScarries no equivalent caveat, despite being in the identical situation (registered, validated, acceptable at subscription time, but with zero working dispatch path).src/services/webhookDispatcher.js:180-191(sendTest): confirmed the hardcodedconst eventType = 'pool.assets_locked';used for all test deliveries regardless of what the webhook is actually subscribed to.POOL_EVENTSexists inwebhookEvents.jsat all despite the indexer having since pivoted entirely to airdrop events.Additional edge cases
pool.*subscriptions (impossible to verify from source alone, but plausible given the README explicitly walks a new integrator through doing exactly that), removingPOOL_EVENTSfrom the valid-subscription set would need either a migration step (deactivating or flagging those existing webhooks) or, at minimum, a clear operational note — simply changing validation going forward wouldn't retroactively inform anyone who already configured a dead subscription based on the README's current instructions.openapi.yamldocumentspool.*events the same wayREADME.mddoes (I did not findpool.references inopenapi.yamlduring this review, suggesting the OpenAPI spec and the README may already disagree with each other independent of this issue — worth a quick audit while fixing this, since whichever document is treated as authoritative should be updated consistently).Test/reproduction plan
POST /webhookswithevents: ["pool.assets_locked"]exactly per the README's own example; confirmwebhookCreateBodySchemaaccepts it (it does today).dispatch()with apool.*event type; confirm there are none.dispatch()→ delivery), or confirm the schema rejectspool.*subscriptions with a clear error, or confirm the README/code comments now accurately flag the gap.Cross-references
pool.*events are a live, exercised code path (matching the framing already used in existing issue Dispatcher webhook signatures have no timestamp or nonce — signed payloads are permanently replayable #97's own description, "everypool.*event delivery"); this issue's finding — that no such deliveries currently occur at all — is useful context for anyone triaging those, since their real-world exposure today is scoped entirely toairdrop.faileddeliveries (and, once implemented, whicheverAIRDROP_EVENTSmembers eventually get wired up), notpool.*traffic.POOL_EVENTSexists at all.