Skip to content

Add server-only webhook plugin with deduplication#8427

Open
MtlPhil wants to merge 3 commits intonightscout:devfrom
MtlPhil:wip/custom-webhook
Open

Add server-only webhook plugin with deduplication#8427
MtlPhil wants to merge 3 commits intonightscout:devfrom
MtlPhil:wip/custom-webhook

Conversation

@MtlPhil
Copy link
Copy Markdown

@MtlPhil MtlPhil commented Feb 8, 2026

TL;DR

This change adds an opt-in, server-only webhook plugin that emits a push-based notification when Nightscout processes a new blood glucose entry. It provides a low-latency mechanism for external services to react to new data without relying on polling, while keeping all follow-up actions outside of Nightscout’s scope. The plugin is disabled by default, runs exclusively on the server, and does not affect existing behavior or the client bundle.

Problem

Some downstream consumers require a push-based signal when new blood glucose data is ingested by Nightscout. Nightscout does not currently expose a simple, low-latency mechanism to notify custom local services immediately when a new glucose entry is recorded.

Polling for new entries is inefficient and introduces unnecessary delay for integrations that need to react in near real time.

Motivation

This change supports architectures where Nightscout acts as a data source and an external service reacts to new glucose data as it becomes available. A common pattern is to run such a service locally (for example, on a Raspberry Pi) and perform follow-up actions outside of Nightscout’s scope.

Providing a webhook at the point where new glucose entries are processed allows these integrations to be implemented cleanly without modifying core Nightscout behavior or relying on polling.

Solution

Introduce a server-only webhook plugin that is invoked when Nightscout processes a new blood glucose entry. The plugin sends an HTTP request to a configurable endpoint, enabling external systems to react immediately.

The implementation:

  • Uses at-least-once delivery semantics with deduplication safeguards
  • Runs exclusively on the server and is excluded from the client bundle
  • Is opt-in and disabled by default

Backward compatibility

  • No breaking changes
  • No impact on existing installations unless explicitly enabled

Testing

  • Webpack build passes
  • ESLint run on touched files (lib/plugins/index.js,
    lib/plugins/webhook.js) with no findings
  • Full test suite not run locally due to missing my.test.env

@AndyLow91
Copy link
Copy Markdown
Member

Thanks for the PR. The general idea seems useful, but I don’t think it’s ready to merge yet.

A few issues need to be addressed first:

  • The PR description says this provides at-least-once delivery semantics, but the current implementation does not. lastSentMills is updated before the webhook request succeeds, so a timeout or request failure causes that SGV to be dropped permanently rather than retried.
  • The current implementation appears to send a webhook on startup for the latest existing SGV, not only when a newly ingested reading is processed. That restart behavior needs to be clarified and handled explicitly.
  • The webhook URL is always built as http://..., so HTTPS endpoints are not actually configurable even though the code branches on protocol.
  • Non-200 responses are treated as failures, but valid webhook receivers may return other success codes such as 202 or 204.
  • Please add tests covering deduplication/retry behavior and startup behavior.

If you want to update the PR to address those points, we can take another look.

- Fix at-least-once delivery: lastSentMills now updated only after a
  successful (2xx) response; failed requests are retried next cycle
- Add pendingMills guard to prevent duplicate concurrent requests
- Suppress startup webhook: first checkNotifications call records the
  current SGV as already seen without firing, so only newly ingested
  readings trigger webhooks
- Add WEBHOOK_PROTOCOL env var (default: http) so HTTPS endpoints are
  actually reachable; URL is now built from the chosen protocol
- Accept any 2xx status code as success (202, 204, etc.)
- Add tests/webhook.test.js with 10 tests covering deduplication,
  retry-on-failure, startup skip, payload shape, and edge cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MtlPhil
Copy link
Copy Markdown
Author

MtlPhil commented Mar 23, 2026

Thanks for the detailed review. Here's what was addressed in the latest commit:

1. At-least-once delivery
lastSentMills is now only updated inside the success callback, after a confirmed 2xx response. A failed or timed-out request leaves lastSentMills unchanged so the next checkNotifications cycle will retry. A separate pendingMills variable prevents duplicate concurrent requests for the same SGV while one is already in flight.

2. Startup behaviour
The first checkNotifications call now records the current SGV as already seen (via an initialized flag) without sending a webhook. Only readings ingested after the process starts will fire.

3. HTTPS support
Added a WEBHOOK_PROTOCOL env var (default: http). The URL is now built as `${WEBHOOK_PROTOCOL}://${WEBHOOK_HOST}:${WEBHOOK_PORT}${WEBHOOK_PATH}`, so setting WEBHOOK_PROTOCOL=https actually routes through the https module.

4. 2xx success codes
The success check is now statusCode >= 200 && statusCode < 300, so 202 and 204 responses are treated as successful deliveries.

5. Tests
Added tests/webhook.test.js with 10 tests covering:

  • Startup skip (no webhook for pre-existing SGV)
  • First new reading after startup fires correctly
  • Deduplication (same mills only sends once)
  • In-flight guard (no duplicate concurrent request)
  • Retry after failure (at-least-once semantics)
  • No retry after success
  • Payload shape
  • Edge cases (null sbx, missing mgdl/mills)

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.

2 participants