Guide to LearnUpon Webhooks: Features and Best Practices
LearnUpon is a learning management system used for employee training, customer education and selling courses to external learners. Training data rarely stays inside the LMS: completions feed HR records and compliance reports, enrolments drive CRM updates, and course purchases need to reach finance. LearnUpon's webhooks push those changes out as they happen.
This guide covers LearnUpon's Webhooks v2.0: getting them enabled, the 22 webhook types, the batched body format, verifying X-Webhook-Signature, and the short response window.
What are LearnUpon webhooks?
Webhooks aren't on by default. LearnUpon support enables them for your portal, and then a portal admin sets up the endpoint URL, a secret key and the webhook types to send.
There are two versions. Version 1 is the older scheme with a different signature; version 2.0 has breaking changes and is what this guide covers. Both can run in parallel, so if you already consume v1, give v2.0 its own endpoint.
A v2.0 delivery is a JSON POST whose body is {"data": [...]}, an array of up to 10 events of the same type. The type itself is in the X-Webhook-Type header, not the body. The body is signed with HMAC-SHA256, and the hex digest arrives in X-Webhook-Signature.
LearnUpon webhook features
| Feature | Details |
|---|---|
| Availability | Enabled per portal by LearnUpon support |
| Version | 2.0 (X-LearnUpon-Version: 2.0) |
| Signature | hex HMAC-SHA256 |
| Signature header | x-webhook-signature |
| Signed content | The raw request body |
| Key | A secret key you set in the webhook configuration |
| Body | JSON: { "data": [ ... ] }, up to 10 events of one type |
| Event routing | Header field X-Webhook-Type |
| Delivery headers | X-Webhook-Id, X-Webhook-Attempt, X-Webhook-Last-Attempt-At |
| Timeout | Ideally 2 seconds; no response within 7 seconds is a failure |
| Retries | For 72 hours: 10 min, 3 h, 12 h, 24 h, 48 h |
| Concurrency | Up to 5 parallel connections |
Events and payloads
| Event | Triggered When | Common Use Cases |
|---|---|---|
user_created | An admin creates a user account | Provision the user in another system |
user_updated | Someone updates a user account's details | Keep profiles in sync |
course_cloning_complete | An admin finishes cloning a course | Update a course catalogue |
course_completion | A learner completes a course | Issue certificates, update HR records |
course_updated | Someone updates a course | Refresh catalogue data |
module_complete | A learner completes a module within a course | Track progress |
purchase_completion | A customer completes a purchase of courses | Fulfilment, accounting |
badge_awarded | The portal awards a badge | Recognition feeds |
badge_revoked | An admin removes a badge from a learner | Revoke recognition |
course_enrollment | Someone enrolls a learner on a course | Notify the learner, sync rosters |
course_unenrollment | Someone unenrolls a learner from a course | Remove from rosters |
learning_path_enrollment | Someone enrolls a learner on a learning path | Sync rosters |
learning_path_updated | Someone updates a learning path | Refresh catalogue data |
learning_path_completion | A learner completes a learning path | Issue credentials |
event_session_creation | Someone creates a live-learning event session | Calendar sync |
event_session_updated | Someone updates a live-learning event session | Calendar sync |
event_session_canceled | Someone cancels a live-learning event session | Calendar sync |
learning_ | Someone enrolls a learner on a learning journey | Sync rosters |
learning_ | A learner completes a learning journey | Issue credentials |
learning_journey_blocked | A learner's journey enrolment is blocked | Alert an admin |
learning_journey_deleted | An admin deletes a learning journey | Clean up |
learning_ | An admin archives a learning journey | Clean up |
LearnUpon also documents a "Live learning event session series created" webhook with a sample, but doesn't list its type value, so you'll need to see a real delivery to route it.
Event Payload Structure
Each item in data is one event. Most carry eventCreated (a UTC timestamp) plus fields specific to the type. User-related events include a user object with userId, email, username and other profile fields, and enrolment and completion events identify the course, learning path or journey involved.
Several of LearnUpon's published samples (for example course_updated, badge_awarded and learning_path_completion) contain trailing commas and aren't valid JSON, so don't copy them into test fixtures as they are. Capture real deliveries instead.
Setting up LearnUpon webhooks
Prerequisites
- A LearnUpon portal with webhooks enabled by LearnUpon support
- A publicly reachable HTTPS endpoint
Get Your Secret
You choose the secret, and LearnUpon uses it to sign every v2.0 delivery.
- Generate a long random value, for example
openssl rand -hex 32. - Paste it into the secret field of the webhook configuration, without trailing spaces.
- Store the same value in your environment:
WEBHOOK_SECRET=your-webhook-secret
Register Your Endpoint
Only Webhooks v2.0 are supported. Ask LearnUpon support to turn them on.
Once support has enabled the feature, configure version 2.0 in your portal, following the version 2.0 instructions in the LearnUpon Knowledge Base (linked from the webhooks v2.0 guide). You'll provide:
- Your endpoint URL, for example
https://example.com/webhooks/learnupon. LearnUpon recommends HTTPS. - The secret key from above.
- The webhook types you want, such as
user_created,course_enrollmentandcourse_completion.
If your framework checks CSRF tokens on POST requests (Rails, Django), exempt the webhook route.
Test a delivery
Start a local receiver and forward requests with the Hookdeck CLI:
npx hookdeck-cli listen 3000 learnupon --path /webhooks/learnupon
Configure the CLI's URL in the portal, then enrol a test learner on a course. Check X-Webhook-Type, X-Webhook-Id and the shape of the data array on the first delivery.
Securing LearnUpon webhooks
Compute HMAC-SHA256 of the raw body with the secret key as UTF-8, hex-encode the result, and compare it with X-Webhook-Signature in constant time. LearnUpon's v2.0 page includes Java and Python samples with a test secret, body and expected signature, which make a good unit test.
const crypto = require('node:crypto');
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const signature = headers['x-webhook-signature'];
if (typeof signature !== 'string') return false;
const key = Buffer.from(secret, 'utf8');
const message = rawBody;
const candidates = [signature];
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
const received = Buffer.from(candidate, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
The handler loops over the batch, routes on the header, and skips a delivery it has already processed using X-Webhook-Id.
const express = require('express');
function createLearnUponApp({ alreadyHandled, handlers }) {
const app = express();
app.post('/webhooks/learnupon', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
const webhookType = req.headers['x-webhook-type'];
const webhookId = req.headers['x-webhook-id'];
let parsed;
try {
parsed = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return res.sendStatus(400);
const { data } = parsed;
if (!Array.isArray(data)) return res.sendStatus(400);
try {
if (await alreadyHandled(webhookId)) return res.sendStatus(200);
const handler = handlers[webhookType];
if (!handler) {
console.log('Unhandled LearnUpon webhook type', webhookType);
return res.sendStatus(200);
}
for (const item of data) await handler(item);
} catch {
return res.sendStatus(503);
}
return res.sendStatus(200);
});
return app;
}
LearnUpon wants an answer in about 2 seconds, so in production, store the batch and run handler in a background worker rather than inside the request.
X-LearnUpon-TimeStamp says when the webhook was generated, but it isn't covered by the signature, so it can't prove freshness. Replays verify like any other delivery; X-Webhook-Id and the event fields are what you deduplicate on.
On a LearnUpon source, Hookdeck verifies X-Webhook-Signature over the raw body with your secret key. LearnUpon always receives 200. A failed signature is recorded as failed verification, and that request goes no further. Hookdeck applies no timestamp tolerance, and v1 webhooks aren't supported.
Make LearnUpon webhooks production-ready. Hookdeck Event Gateway answers LearnUpon well within 7 seconds, verifies each batch, and keeps every completion and enrolment replayable beyond LearnUpon's 72-hour retry window.
LearnUpon webhook limitations and pain points
A tight response window
The Problem: Deliveries are marked failed and retried even though your handler eventually succeeds.
Why It Happens: LearnUpon would like a 2xx within 2 seconds and treats no response within 7 seconds as a failure. A batch of 10 events processed in-line can easily exceed that.
Workarounds:
- Persist the body and respond before doing any work.
- Process the
dataitems from a queue.
How Hookdeck Can Help: Hookdeck replies to LearnUpon immediately and delivers to your service with a longer timeout of your choosing.
Batched bodies
The Problem: A failure on one event causes the whole batch to be retried, repeating work for the other nine.
Why It Happens: One request can carry up to 10 events, and the response covers all of them.
Workarounds:
- Make each item's processing idempotent, keyed on the event's own identifiers and
eventCreated. - Split the batch into individual jobs once it's verified.
How Hookdeck Can Help: Hookdeck stores each batch once and retries it to your service on your schedule, and its log shows which batch failed and the response your service gave.
Support-gated setup and two versions
The Problem: You can't self-serve webhooks, and v1 and v2.0 traffic can get mixed up.
Why It Happens: Support has to enable webhooks, and both versions can run in parallel with different signature schemes.
Workarounds:
- Ask support for v2.0 specifically, and use separate URLs for v1 and v2.0.
- Check
X-LearnUpon-Versionbefore verifying.
How Hookdeck Can Help: A dedicated Hookdeck LearnUpon source handles v2.0 only, and its log shows any v1 request that ends up there as a failed verification.
Gaps in the type list and samples
The Problem: Some deliveries can't be routed, and some fixtures won't parse.
Why It Happens: No example X-Webhook-Type value is shown, one documented webhook has no type value, and several samples aren't valid JSON.
Workarounds:
- Log unknown types with their bodies and return
2xx. - Build fixtures from real deliveries.
How Hookdeck Can Help: Hookdeck stores each delivery's headers and body, so the real type values and payloads are available as soon as LearnUpon sends them.
Best practices
Use the published test vector
LearnUpon's Python sample gives a secret, a body and the expected signature. Put them in a unit test to prove your verifier before connecting a portal.
Recover with the retention window
LearnUpon retries for 72 hours and retains webhooks for 45 days. If your endpoint was down for longer than the retry window, contact LearnUpon support about the retained webhooks before rebuilding data by hand.
Separate processing from receipt
Return 2xx as soon as the batch is stored. Our guide to asynchronous processing explains why this matters for short timeouts like LearnUpon's.
Make items idempotent
Retries resend the whole batch. Record what each item changed, using the approach in implementing webhook idempotency.
Conclusion
LearnUpon's v2.0 webhooks are well specified on the signature side, with a test vector to prove it. The practical challenges are operational: getting support to enable them, keeping v1 and v2.0 apart, answering within seconds, and handling batches of up to 10 events that are retried together.
Hookdeck Event Gateway removes the timeout pressure and gives each LearnUpon batch a stored, replayable record.
Get started with Hookdeck for free and handle LearnUpon webhooks reliably in minutes.