Gareth Wilson Gareth Wilson

Guide to LearnUpon Webhooks: Features and Best Practices

Published


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

FeatureDetails
AvailabilityEnabled per portal by LearnUpon support
Version2.0 (X-LearnUpon-Version: 2.0)
Signaturehex HMAC-SHA256
Signature headerx-webhook-signature
Signed contentThe raw request body
KeyA secret key you set in the webhook configuration
BodyJSON: { "data": [ ... ] }, up to 10 events of one type
Event routingHeader field X-Webhook-Type
Delivery headersX-Webhook-Id, X-Webhook-Attempt, X-Webhook-Last-Attempt-At
TimeoutIdeally 2 seconds; no response within 7 seconds is a failure
RetriesFor 72 hours: 10 min, 3 h, 12 h, 24 h, 48 h
ConcurrencyUp to 5 parallel connections

Events and payloads

EventTriggered WhenCommon Use Cases
user_createdAn admin creates a user accountProvision the user in another system
user_updatedSomeone updates a user account's detailsKeep profiles in sync
course_cloning_completeAn admin finishes cloning a courseUpdate a course catalogue
course_completionA learner completes a courseIssue certificates, update HR records
course_updatedSomeone updates a courseRefresh catalogue data
module_completeA learner completes a module within a courseTrack progress
purchase_completionA customer completes a purchase of coursesFulfilment, accounting
badge_awardedThe portal awards a badgeRecognition feeds
badge_revokedAn admin removes a badge from a learnerRevoke recognition
course_enrollmentSomeone enrolls a learner on a courseNotify the learner, sync rosters
course_unenrollmentSomeone unenrolls a learner from a courseRemove from rosters
learning_path_enrollmentSomeone enrolls a learner on a learning pathSync rosters
learning_path_updatedSomeone updates a learning pathRefresh catalogue data
learning_path_completionA learner completes a learning pathIssue credentials
event_session_creationSomeone creates a live-learning event sessionCalendar sync
event_session_updatedSomeone updates a live-learning event sessionCalendar sync
event_session_canceledSomeone cancels a live-learning event sessionCalendar sync
learning_journey_enrollmentSomeone enrolls a learner on a learning journeySync rosters
learning_journey_completionA learner completes a learning journeyIssue credentials
learning_journey_blockedA learner's journey enrolment is blockedAlert an admin
learning_journey_deletedAn admin deletes a learning journeyClean up
learning_journey_archivedAn admin archives a learning journeyClean 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.

  1. Generate a long random value, for example openssl rand -hex 32.
  2. Paste it into the secret field of the webhook configuration, without trailing spaces.
  3. 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:

  1. Your endpoint URL, for example https://example.com/webhooks/learnupon. LearnUpon recommends HTTPS.
  2. The secret key from above.
  3. The webhook types you want, such as user_created, course_enrollment and course_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 data items 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-Version before 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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.