Skip to content

Add configurable custom notification webhooks (#5742) - #8580

Open
joshi-rushikesh wants to merge 4 commits into
nightscout:devfrom
joshi-rushikesh:wip/custom-webhook-5742
Open

Add configurable custom notification webhooks (#5742)#8580
joshi-rushikesh wants to merge 4 commits into
nightscout:devfrom
joshi-rushikesh:wip/custom-webhook-5742

Conversation

@joshi-rushikesh

@joshi-rushikesh joshi-rushikesh commented Aug 11, 2026

Copy link
Copy Markdown

Closes #5742

Why

Issue #5742 describes delays caused by routing Nightscout notification events through IFTTT Maker. Today, the Maker notification path is tied to an IFTTT key and a hardcoded IFTTT destination, so operators cannot route those same notification events directly to their own endpoints.

This PR adds an independent custom notification-webhook path while preserving the existing Maker/IFTTT behavior.

What changed

Operators can configure up to four URL/event pairs:

CUSTOM_WEBHOOK_URL_1="https://my-endpoint.example.com/nightscout"
CUSTOM_WEBHOOK_EVENT_1="ns-urgent"

CUSTOM_WEBHOOK_URL_2="http://192.168.1.50:3000/nightscout"
CUSTOM_WEBHOOK_EVENT_2="ns-event"

Matching notifications are delivered directly to the configured endpoint as JSON POST requests.

The implementation:

  • adds a dedicated lib/server/customwebhook.js sender
  • dispatches custom webhooks independently from Maker in pushnotify
  • uses Nightscout's existing numbered-settings convention for configuration
  • supports both HTTP and HTTPS
  • uses a 5-second timeout and treats any 2xx response as successful
  • skips malformed or incomplete configuration without preventing startup
  • deduplicates by destination so one notification is not accidentally delivered multiple times to the same endpoint
  • protects configured webhook URLs through secureSettings
  • avoids logging full webhook URLs or notification contents
  • leaves lib/plugins/maker.js and lib/plugins/webhook.js unchanged

Design notes

Independent from Maker / IFTTT

lib/plugins/maker.js is intentionally not modified.

Maker's existing three-event fan-out (ns-event, ns-<level>, ns-<level>-<name>) is an IFTTT-specific compatibility behavior that dates back to the original implementation. Extending the Maker sender directly would also inherit the requirement for a MAKER_KEY.

Instead, this PR adds a sibling delivery path from the notification dispatcher. Custom webhooks therefore work even when ctx.maker === null, while existing Maker behavior remains unchanged.

Configuration naming

The issue illustrates names such as CUSTOM_WEBHOOK_1_URL, but Nightscout's current numbered-setting convention maps naturally to:

CUSTOM_WEBHOOK_URL_1
CUSTOM_WEBHOOK_EVENT_1

This matches existing settings such as FRAME_URL_1 and avoids changing the global settings-name parser.

Event matching

Custom webhook event matching follows Nightscout's existing Maker event vocabulary:

  • ns-event
  • ns-<level>
  • ns-<level>-<name>
  • ns-allclear

For low blood glucose alarms, the level-qualified forms are used, for example:

ns-urgent-low
ns-warning-low

There is no bare ns-low event in the current event vocabulary.

Delivery behavior

A single configured destination is sent at most one request for a notification, even if more than one generated event name would match that same destination.

The JSON payload includes the notification fields needed by the receiving endpoint, and the transport uses Node's built-in http, https, and URL APIs. No new dependencies were added.

Security considerations

Webhook URLs may contain credentials or tokens, so the new URL settings are included in secureSettings and are not exposed through normal serialized settings output.

The implementation also:

  • only accepts http: and https: schemes
  • does not log full URLs, query strings, titles, or message bodies
  • skips malformed URLs rather than crashing startup
  • reports network failures through callbacks rather than throwing into the alarm path

Custom webhook URLs are operator-controlled configuration. They can point to private-network hosts, which is intentional for this use case and consistent with Nightscout's existing server webhook functionality. This is documented because it creates the expected SSRF surface of an operator-configurable outbound webhook feature.

Backward compatibility

The default configuration contains no custom webhook destinations, so the new module initializes to null and produces no additional outbound traffic unless explicitly configured.

Existing behavior is preserved:

  • lib/plugins/maker.js is unchanged
  • lib/plugins/webhook.js is unchanged
  • existing Maker key handling is unchanged
  • existing Maker three-request fan-out is unchanged
  • existing SGV webhook behavior is unchanged
  • custom webhook delivery does not depend on MAKER_KEY
  • custom delivery does not modify the existing recentlySent TTL or Pushover receipt handling

Testing

Focused feature tests

npx env-cmd -f ./my.test.env npx mocha --timeout 5000 --require ./tests/hooks.js --exit ./tests/customwebhook.test.js

Result:

26 passing
0 failing

Related regression suites

npx env-cmd -f ./my.test.env npx mocha --timeout 5000 --require ./tests/hooks.js --exit ./tests/maker.test.js ./tests/webhook.test.js ./tests/settings.test.js ./tests/env.test.js

Relevant results:

maker:        6 passing
webhook:     10 passing
settings:    16 passing
combined:    55 passing, 0 failing

The existing Maker and SGV-webhook test files were not modified.

Broader unit suite

The broader local unit suite produced:

293 passing
6 failing

The six failures are MongoDB-dependent local-environment failures in the existing security/authentication tests. To verify they were not introduced by this change, I temporarily removed the custom-webhook source changes and re-ran the failing suites; the same four verifyauth timeouts and two security failures remained.

Lint

npm run lint reports the same 32 pre-existing problems with or without the custom-webhook source changes.

Running ESLint against the new/modified source and test files produces no new lint problems.

Manual transport verification

Because automated tests stub the outbound request seam, I also exercised the real transport against a local HTTP listener:

204 response -> success
500 response -> failure
dead port -> callback error, no throw
query string -> preserved
JSON body -> delivered
custom delivery with ctx.maker === null -> successful

Acceptance criteria

  • Tests added for new/changed behavior
  • Relevant feature and regression suites pass
  • New and modified files introduce no new lint problems
  • Existing Maker behavior remains unchanged
  • Existing SGV-webhook behavior remains unchanged
  • No breaking changes intentionally introduced
  • Documentation updated
  • Pull request targets upstream dev
  • Original issue referenced with Closes #5742
  • Full local unit suite completely green — 6 MongoDB-dependent failures remain, but they were reproduced without the feature changes and documented as pre-existing local-environment failures

Before / after verification evidence

Before implementation, reproducing an urgent-low notification through the existing path generated three requests to the hardcoded IFTTT Maker endpoint and zero requests to the configured custom endpoint:

maker.ifttt.com requests: 3
configured custom endpoint requests: 0

After implementation:

customwebhook tests: 26 passing
related Maker/webhook/settings/env suites: 55 passing, 0 failing
custom delivery with ctx.maker === null: 1 destination delivered

The existing Maker and SGV-webhook implementations remain unchanged.

Documentation

This PR updates:

  • README.md with a new Custom Notification WebHooks section
  • docs/example-template.env with commented configuration examples

The README documents the configuration variables, event matching behavior, JSON delivery behavior, low/high alarm event naming, and security considerations.

joshi-rushikesh and others added 4 commits August 10, 2026 23:38
Nightscout notifications could only be delivered to IFTTT Maker. The
destination host is hardcoded in maker.makeKeyRequest and the whole path
returns null without a MAKER_KEY, so operators could not send events to
their own endpoint (issue nightscout#5742).

Add a sibling delivery path for operator-configured destinations:

- CUSTOM_WEBHOOK_URL_1..4 / CUSTOM_WEBHOOK_EVENT_1..4 declared in
  lib/settings.js, following the existing numbered frameUrl/frameName
  pattern so nameFromKey maps them without changes to env.js
- lib/server/customwebhook.js normalizes the pairs, skipping unused and
  incomplete slots and rejecting anything that is not a valid http or
  https URL, so bad configuration warns instead of failing startup
- matching reuses the ns-event / ns-<level> / ns-<level>-<name> names
  documented for Maker, but sends one request per destination rather
  than Maker's three, which exist only to work around IFTTT filtering
- delivery is POST JSON over http or https with an explicit timeout,
  any 2xx treated as success, and network errors reported through the
  callback so a failed webhook cannot break the notification path

Custom delivery is not gated on ctx.maker, so it works with no
MAKER_KEY, and it does not touch the recentlySent TTL. lib/plugins/maker.js
is unchanged, so existing MAKER_KEY and MAKER_ANNOUNCEMENT_KEY behavior
is identical. The SGV-oriented lib/plugins/webhook.js is also unchanged.

Webhook URLs are added to secureSettings because /api/v1/status publishes
env.settings, and these URLs commonly embed per-destination tokens. Logs
record the origin only, never the full URL or notification content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/customwebhook.test.js covers configuration parsing, event matching
and transport, replacing customwebhook.sendRequest so no test performs a
real network request, following the seam tests/webhook.test.js already
uses for the SGV webhook plugin.

Configuration: no config, absent settings, url without event, event
without url, malformed url, non http(s) scheme, one invalid entry not
discarding the valid ones, sparse indexes and whitespace trimming.

Matching: the generated ns-event / ns-<level> / ns-<level>-<name> names,
a match delivering exactly one request to the configured destination, a
non-match delivering nothing, several destinations matching one
notification, a single destination being sent once even when two of its
configured event names match, missing name and level rejection, and
allclear reaching only the destinations configured for it.

Payload and transport: payload fields, announcement flag, parsed http and
https targets including port, path and query, a network failure surfaced
through the callback rather than thrown, and a partial failure that still
counts the successful send while keeping the secret bearing path out of
the error text.

tests/settings.test.js adds coverage for the numbered CUSTOM_WEBHOOK_URL_n
and CUSTOM_WEBHOOK_EVENT_n env var mapping, including a sparse index, and
asserts filteredSettings does not publish the URLs in status.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add a "Custom Notification WebHooks" section to the README next to the
IFTTT Maker section, covering what the feature does and why it exists,
the numbered CUSTOM_WEBHOOK_URL_n and CUSTOM_WEBHOOK_EVENT_n variables,
a worked two destination example, the event matching rules including the
one request per destination guarantee, the JSON request body, and the
operational notes on sparse numbering, skipped invalid entries, timeout
and 2xx handling.

State explicitly that the feature is independent of the IFTTT Maker
integration and needs no MAKER_KEY, and that it is separate from the SGV
webhook plugin, so operators are not left guessing which one they want.

Document the security posture: URLs are secure settings and are not
published in /api/v1/status, logs contain the destination host only, and
these variables let the server reach any address it can route to.

Add the corresponding TOC entry and commented examples in
docs/example-template.env.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The event matching documentation used ns-urgent-simplealarms as its
example of the ns-<level>-<name> form, but that name is never generated.
simplealarms sets eventName to low or high on every alarm branch, and
pushnotify uses notify.eventName in preference to the plugin name, so
the plugin name fallback never applies to BG alarms.

Use ns-urgent-low as the example instead and spell out that low and high
BG alarms are level qualified: ns-urgent-low for BG_LOW, ns-warning-low
for BG_TARGET_BOTTOM, and the matching high variants. Also state that
there is no bare ns-low event, since issue nightscout#5742 illustrates the feature
with that name, and point at the existing maker setup event list for the
full vocabulary.

Documentation only, no behavior change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joshi-rushikesh

Copy link
Copy Markdown
Author

Hi @AndyLow91 — this is my first contribution to Nightscout. I implemented #5742 by adding configurable notification-event webhooks as an independent delivery path while preserving the existing Maker/IFTTT behavior. I also added focused tests and documentation. Since you reviewed the related server-webhook contribution #8427, I’d really appreciate a review when you have time. Thank you!

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.

1 participant