Skip to content

Notifications & events

PulseLMS keeps people informed two ways, and integrators a third:

  • In-app notifications: the bell in the top bar, with an unread badge.
  • Email: branded transactional email for the important types.
  • Webhooks: HMAC-signed HTTP callbacks to your own endpoints, for system-to-system integration.

This page catalogs all three.


In-app & email notification types

Each notification has a type, a title, an optional body, and a link that opens the relevant screen in PulseLMS. Every type appears in the bell. Types marked Email are also sent by branded email, subject to the recipient's preferences.

Category is the switch a member uses to control that email, under My Account → Email notifications. Types in the same category share one switch.

Type Trigger Goes to Category Email
assigned A course or program is assigned to you. Learner Assignments
due_soon An assignment's due date is approaching. Learner Due dates and reminders
overdue An assignment has passed its due date. Learner Due dates and reminders
nudge A manager or instructor nudges you to keep going. Learner Due dates and reminders
graded An instructor grades your quiz attempt. Learner Grades and progress
reset A quiz reset / retake request is approved. Learner Grades and progress
certificate A certificate is issued to you. Learner Certificates
cert_expiring A certificate you hold is nearing its expiry date. Learner Certificates
recert_due A recertification cycle requires you to retake a course. Learner Certificates
completed You complete a course or program. Learner Course activity
course_updated A course you're enrolled in is republished with changes. Learner Course activity
reply Someone replies to your discussion post or question. Learner Discussions

Preferences

Each person chooses which categories reach them by email under My Account, and can switch email off entirely from the bell menu. In-app notifications always appear in the bell regardless; preferences govern email only. Delivery is scheduled by a background worker, including a daily digest for reminder-style types.

Why some emails don't send: active-user suppression

Reminder emails (like due_soon) are suppressed for people who are already active in PulseLMS around that time; the goal is to nudge the people who've gone quiet, not to spam people who are already working. See the FAQ.


Webhooks

Webhooks let an org admin register HTTPS endpoints that PulseLMS calls when something happens: enroll someone in your HR system when they complete a course, sync certificates to a compliance tool, and so on. Configure them under Settings → Integrations, where you can also send a test delivery.

Event types

Event Fires when
enrollment.assigned A learner is enrolled in / assigned a course.
enrollment.completed A learner completes a course.
certificate.issued A certificate is issued.
course.published An author publishes (snapshots a new version of) a course.
program.completed A learner finishes every course in a program.

An endpoint can subscribe to specific events or to all events.

Delivery & payload

Each delivery is a JSON POST to your endpoint URL. The body has a stable shape:

{
  "event": "enrollment.completed",
  "timestamp": "2026-07-23T14:05:00+00:00",
  "data": {
    "enrollment_id": "…",
    "course_id": "…",
    "user_sub": "…"
  }
}

Every request carries these headers:

Header Value
Content-Type application/json
X-PulseLMS-Event The event type, e.g. enrollment.completed.
X-PulseLMS-Signature sha256=<hex>; see below.

Delivery is best-effort: a failing endpoint never blocks or rolls back the action that triggered it. An endpoint that fails 10 times in a row is automatically disabled; re-enable it from Settings once it's healthy. Respond with any 2xx status to acknowledge.

Verifying the signature

The X-PulseLMS-Signature header is sha256= followed by the hex HMAC-SHA256 of the exact raw request body bytes, keyed with your endpoint's signing secret (whsec_…, shown once when you create the endpoint). Compute the HMAC over the raw body before any JSON parsing, and compare with a constant-time equality check.

import hmac, hashlib
from flask import request, abort

ENDPOINT_SECRET = "whsec_…"  # from Settings → Integrations

@app.post("/pulselms-webhook")
def receive():
    raw = request.get_data()  # exact bytes, do not re-serialize
    sent = request.headers.get("X-PulseLMS-Signature", "")
    expected = "sha256=" + hmac.new(
        ENDPOINT_SECRET.encode(), raw, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sent, expected):
        abort(400, "bad signature")
    event = request.headers["X-PulseLMS-Event"]
    payload = request.get_json()
    # … handle event …
    return "", 200
const crypto = require("crypto");
const ENDPOINT_SECRET = "whsec_…"; // from Settings → Integrations

// Capture the RAW body; verify before parsing.
app.post("/pulselms-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sent = req.get("X-PulseLMS-Signature") || "";
    const expected = "sha256=" + crypto
      .createHmac("sha256", ENDPOINT_SECRET)
      .update(req.body)          // req.body is a Buffer
      .digest("hex");
    const ok = sent.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected));
    if (!ok) return res.status(400).send("bad signature");

    const event = req.get("X-PulseLMS-Event");
    const payload = JSON.parse(req.body.toString());
    // … handle event …
    res.status(200).end();
  });

Verify the raw body

Re-serializing the parsed JSON can reorder keys or change whitespace and will break the signature. Always HMAC the bytes you received.


See also