# Absorb LMS Webhooks

[Absorb LMS](https://www.absorblms.com) is a learning management system. Its webhooks send a
lean JSON notification to your URL when something happens in the LMS: a user is created, a
course is completed, a certificate is awarded. Each request carries an `absorb-signature`
header, an HMAC-SHA256 of the raw request body keyed with the webhook's **Secret Key**.

## When to Use This Skill

* How do I receive Absorb LMS webhooks?
* How do I verify the `absorb-signature` header?
* Why does the Test Webhook button fail signature verification?
* How do I handle `UserCreated`, `CertificateAwarded` or `EnrollmentCompletedbyAdmin` events?
* Why is my Absorb LMS webhook signature verification failing?

## Verification (core)

`absorb-signature` is the **bare lowercase hex** HMAC-SHA256 of the **raw request body**: no
prefix, no timestamp. The key is the Secret Key shown on the saved webhook, used as a UTF-8
string (not hex- or base64-decoded). Check the header is exactly 64 hex characters before
decoding, because `Buffer.from(x, 'hex')` silently truncates invalid input.

```javascript
const crypto = require('crypto');

function verifyAbsorbSignature(rawBody, signature, secret = process.env.ABSORB_WEBHOOK_SECRET) {
  if (!secret || typeof signature !== 'string') return false;
  if (!/^[0-9a-f]{64}$/i.test(signature)) return false;   // also guards the hex decode
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(signature, 'hex'));
}

// rawBody: Buffer from express.raw({ type: '*/*' }); signature: req.headers['absorb-signature']

```

Return `401` when this fails, `400` for a body that isn't a JSON object, and `200` once handled.
In Python use `hmac.compare_digest` on **bytes** (encode both sides) so a non-ASCII header gives
`401`, not `500`. Details in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/absorb-lms-webhooks/references/verification.md).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/absorb-lms-webhooks/examples/express/),
> [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/absorb-lms-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/absorb-lms-webhooks/examples/fastapi/).

## Setup and Test Events

Create the webhook in the Admin Experience under **Account > Client Settings > New Webhook**,
choose the events, enter your URL and save. The **Secret Key** is generated only after the
webhook is saved; reopen it to copy the key into `ABSORB_WEBHOOK_SECRET`. Full steps in
[references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/absorb-lms-webhooks/references/setup.md).

**Hookdeck setup:** Absorb's Test Webhook button sends a random signature, so test events fail
verification. Real deliveries are signed with the Secret Key. When you use an Absorb LMS source
in Hookdeck, expect the Test Webhook request to be rejected and trigger a real event in a
sandbox portal (for example create a user) to check your setup end to end.

## Payload and Event Types

The payload is deliberately lean: it names the event and carries IDs (the learner's User ID,
the Admin ID for admin events, and the Client ID and Correlation ID of the transaction). Call
the Absorb Integration API for the full record. **Dispatch on `eventName`**, which is the
event's display name with the spaces removed.

| `eventName` | Triggered When |
| --- | --- |
| `UserCreated` | An admin creates a user |
| `OnlineCourseActivated` | An online course is activated |
| `EnrollmentCompletedbyAdmin` | An admin completes an enrollment |
| `EnrollmentCompletedbyLearner` | A learner completes an enrollment |
| `CertificateAwarded` | A certificate is awarded |
| `CompetencyAwarded` | A competency is awarded |
| `CourseCreditsAwarded` | Course credits are awarded |
| `UserEnrolledinCoursebyAdmin` / `UserEnrolledinCoursebyLearner` | A user is enrolled in a course |

Other events (departments, groups, curriculums, ILC courses, bundles, sessions, learning
paths) are listed in [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/absorb-lms-webhooks/references/overview.md).

## Environment Variables

```bash
ABSORB_WEBHOOK_SECRET=your_webhook_secret_key   # the Secret Key on the saved webhook

```

## Local Development

```bash
npx hookdeck-cli listen 3000 absorb-lms --path /webhooks/absorb-lms

```

No account required. Use the printed URL as the webhook's URL in Absorb.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/absorb-lms-webhooks/references/overview.md) - How Absorb LMS webhooks work, events, payload
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/absorb-lms-webhooks/references/setup.md) - Create the webhook, get the Secret Key, Test Webhook limits
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/absorb-lms-webhooks/references/verification.md) - HMAC-SHA256 check, gotchas, debugging
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/absorb-lms-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests