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 | |
|---|---|---|---|---|
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¶
- Partner API: pair webhooks with API keys for a full integration.
- Notifications (learner guide): the learner-facing view of the bell and preferences.
- Integrations (admin guide): registering endpoints and sending test deliveries.