Agent skill
Lessonly Webhooks Skill
Receive and authenticate Lessonly (now Seismic Learning) webhooks. Use when setting up a Lessonly lesson-completed callback endpoint, configuring lesson_completed_callback_authorization, checking the Authorization header, or debugging 401s on a Lessonly webhook.
Install this skill
npx skills add hookdeck/webhook-skills --skill lessonly-webhooks
Lessonly is a training platform, now part of Seismic and branded Seismic Learning; the API and docs still use the Lessonly name. It sends one webhook: a callback when a learner completes a lesson (lesson completed). Payloads are not signed. The only credential is an optional Authorization header whose value you choose.
When to Use This Skill
- How do I receive Lessonly lesson-completed webhooks?
- How do I set
lesson_andcompleted_ callback_ url lesson_?completed_ callback_ authorization - Does Lessonly sign webhook payloads? (No: shared
Authorizationvalue only) - Why does my Lessonly (Seismic Learning) webhook endpoint return 401?
Verification (core)
You set the credential yourself with PUT /api/v1/company_ (lesson_, for example Tm93IGlzIHRo). Lessonly sends it verbatim as Authorization: <value>; it adds no scheme. If you included Bearer or Basic in the value, that prefix is part of it. Compare the whole header to LESSONLY_AUTHORIZATION; don't strip, parse or base64-decode anything:
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyLessonlyRequest(headers, expected = process.env.LESSONLY_AUTHORIZATION) {
if (!expected) return false; // never accept when unconfigured
const received = headers['authorization']; // full value, exactly as configured
return Boolean(received) && safeEqual(received, expected);
}
Return 401 when the header is missing or does not match, 400 for a body that isn't a JSON object, and 200 once handled. Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the check. Python and Next.js equivalents are in references/verification.md.
What this does and doesn't give you: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Anyone who captures a request can resend it. Use HTTPS only, make the handler idempotent, and rotate the credential (set a new lesson_) if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Webhook Events
| Event | Triggered When |
|---|---|
lesson completed | A learner completes a lesson (delivered to lesson_) |
There is no event-type field to switch on: every delivery to the callback URL is a lesson completion. Lessonly's API reference doesn't document the payload, so the JSON body in this skill's examples (lesson_id, user_id, completed_at) is illustrative. Capture a real delivery (the Hookdeck CLI makes that easy) before depending on field names.
Setup Note (Hookdeck)
Hookdeck's Lessonly source (type LESSONLY, labelled "Lessonly") compares the whole Authorization header to the key you configure. Paste the Authorization value exactly as set in Lessonly, including any "Bearer " or "Basic " prefix.
Environment Variables
LESSONLY_AUTHORIZATION=Bearer 00000000-0000-0000-0000-000000000000 # full Authorization value, as set in Lessonly
Local Development
npx hookdeck-cli listen 3000 lessonly --path /webhooks/lessonly
No account required. Use the printed URL as lesson_.
Resources
- references/overview.md - The lesson-completed callback and its (illustrative) payload
- references/setup.md - Configuring the callback with the company settings API
- references/verification.md - Authorization check in Node, Python and Next.js, gotchas
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests