Skip to content

Add LSPS5 webhook notification support - #993

Open
Camillarhi wants to merge 1 commit into
lightningdevkit:mainfrom
Camillarhi:lsps5
Open

Camillarhi wants to merge 1 commit into
lightningdevkit:mainfrom
Camillarhi:lsps5

Conversation

@Camillarhi

@Camillarhi Camillarhi commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Integrates LSPS5 (bLIP-0055) from lightning-liquidity, enabling webhook-based push notifications so clients can be alerted to events while their app is offline. Built on the refactored multi-LSP liquidity module (src/liquidity/{client,service}).

  • set_webhook - register/update a webhook.
  • list_webhooks - query registered webhooks.
  • remove_webhook - remove a registration.

When node_id is None, set_webhook and remove_webhook fan out to every LSPS5-capable LSP, so a webhook can be configured once across all configured LSPs.

Lets a node act as an LSPS5 server:

  • Delivers outgoing webhook notifications over HTTP in response to LSPS5ServiceEvent::SendWebhookNotification.
  • Auto-triggers an onion_message_incoming notification when an intercepted onion message targets a client that is currently offline (wired from LdkEvent::OnionMessageIntercepted, gated on peer connectivity via the peer manager).

Reopening after an accidental force-push pushed the branch to main's tip and auto-closed #729.

Fixes: #1017

@ldk-reviews-bot

ldk-reviews-bot commented Jul 17, 2026

Copy link
Copy Markdown

🎉 This PR is now ready for review!
Please choose at least one reviewer by assigning them on the right bar.
If no reviewers are assigned within 10 minutes, I'll automatically assign one.
Once the first reviewer has submitted a review, a second will be assigned if required.

@ldk-reviews-bot
ldk-reviews-bot requested a review from tnull July 17, 2026 11:59
@Camillarhi
Camillarhi marked this pull request as draft July 17, 2026 12:06
@Camillarhi
Camillarhi force-pushed the lsps5 branch 7 times, most recently from 1d750fb to 9d4f022 Compare August 20, 2026 00:24
@Camillarhi
Camillarhi marked this pull request as ready for review August 24, 2026 14:06
@Camillarhi
Camillarhi force-pushed the lsps5 branch 5 times, most recently from 2601d0f to 9eb148f Compare August 25, 2026 15:18

@f3r10 f3r10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great work 👍

Comment thread src/liquidity/client/lsps5.rs Outdated
},
};

pending_set_webhook_requests_lock.insert(request_id, sender);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LSPS1/LSPS2 wrap the same kind of map in PendingRequest/PendingRequestGuard (see client/lsps2.rs), held across the .await and removing its own entry on drop.
Here nothing removes the entry when tokio::time::timeout(...) in lsps5_set_webhook/lsps5_list_webhooks/lsps5_remove_webhook gives up — the oneshot::Sender and its HashMap entry stay behind for the life of the node. On a node with a slow or flaky LSPS5 LSP this grows unbounded. Same pattern at L124 (pending_list_webhooks_requests) and L176 (pending_remove_webhook_requests)

Could we reuse PendingRequestGuard here, the way LSPS2 does? None of these three calls need the fan-out (followers) side of PendingRequest — only one caller ever awaits a given set_webhook/list_webhooks/remove_webhook — but the drop-cleanup is exactly what's missing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks! This has been updated to use PendingRequestGuard just like LSPS2 does

Comment thread src/liquidity/mod.rs Outdated
let lsps2_service_config =
self.lsps2_service.as_ref().map(|s| s.ldk_service_config.clone());
let lsps5_service_config = self.lsps5_service.clone();
let advertise_service = self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

advertise_service sets the shared LSPS feature bit for any configured service, not just LSPS2 — but this only reads it off lsps2_service. A node with enable_liquidity_provider(None, Some(lsps5_cfg)) always gets advertise_service = false, with no way to turn it on.

Is that intentional, or should LSPS5-only providers be able to advertise too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's reachable. Though the flag sets the shared LSPS0, so it was never really an LSPS2 thing. If I add it to the LSPS5 config too, then LSPS1 service lands, and that's three copies of the same flag. One node-level setting is probably where this should end up, so I'll take a look at that instead of duplicating it

Comment thread src/liquidity/service/lsps5.rs Outdated
e
),
}
Error::LiquidityNotifyWebhookFailed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SlowDownError (the notification cooldown) and every other failure both map to Error::LiquidityNotifyWebhookFailed. A caller can't tell "you're rate-limited, retry shortly" from "this genuinely failed" without parsing logs.

Worth a distinct Error::LiquidityNotifyRateLimited (or similar) so callers can branch on it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks. This will be updated to return a distict error for slow down

Implement the bLIP-55 / LSPS5 webhook registration protocol on top of the
multi-LSP liquidity module (src/liquidity/{client,service}).

Client side, exposed via Node::liquidity().lsps5():
- set_webhook / list_webhooks / remove_webhook to manage webhook
  registrations with a given LSP. Each takes the LSP's node ID explicitly:
  bLIP-55 has the notification service verify the x-lsps5-signature header
  against the signing LSP's node ID, so a registration is meaningful only
  for one LSP at a time.

Service side, enabled by passing an LSPS5ServiceConfig to
Builder::enable_liquidity_provider():
- Deliver outgoing webhook notifications over HTTPS in response to
  LSPS5ServiceEvent::SendWebhookNotification.
- Send lsps5.payment_incoming when an inbound HTLC forward to a client
  fails because the client is offline (wired from LdkEvent::
  HTLCHandlingFailed with LocalHTLCFailureReason::PeerOffline).
- Send lsps5.onion_message_incoming when an intercepted onion message
  targets a client that is currently offline.
- Send lsps5.expiry_soon from a periodic task that scans channels for outbound
  HTLCs approaching their cltv_expiry, so a client that went offline holding an
  HTLC has a chance to come online and settle before it expires.

Adds integration tests covering webhook registration and the
payment_incoming trigger, and wires the feature through the UniFFI
bindings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LSPS5 webhook notification support

3 participants