eLearning.edu.pl API
ENPL

Webhooks

Webhooks push events to your HTTPS endpoint, so you don't have to poll.

Events

Event When
course.completed A person completed or passed a course.
course.failed A course attempt was failed.
compliance.certified A new certificate was issued.
compliance.expired A certificate expired without a successor.
enrollment.assigned A course was assigned (API, rules, admin panel, renewal).
enrollment.cancelled A not-yet-started assignment was cancelled.
group.member_joined / group.member_left Group membership changed.
ping Test event sent on request.

Payload schemas are in the API reference (section Webhooks). Every event has the envelope {"id", "type", "created_at", "data"}.

Subscribe

A client with the webhooks:manage scope manages its own subscriptions. The target host must be on the client's allowed host list set by your IT administrator.

curl https://api.elearning.edu.pl/api/v1/webhooks/subscriptions -H "X-Tenant-Code: your-tenant" -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "HR sync", "target_url": "https://hr.example.com/hooks/training",
       "subscribed_events": ["course.completed", "compliance.expired"]}'

The response contains secret_token once — store it; it signs every delivery. Use POST /api/v1/webhooks/subscriptions/{id}/ping to send a test event.

Target URL rules: public https:// host name on port 443 (no IP addresses, no private addresses). Deliveries come from a fixed platform address; ask your onboarding contact for it if your firewall uses an allowlist.

Verify the signature

Each delivery carries:

X-LMS-Signature: t=1760000000,v1=5d41402abc4b2a76b9719d911017c592…
X-LMS-Event: course.completed
X-LMS-Event-Id: 7f3c2a9e-1b4d-4c8a-9e2f-5a6b7c8d9e01

Compute hex(HMAC-SHA256(secret_token, "{t}.{raw request body}")) and compare it, in constant time, with each v1 value. Reject the request when nothing matches or when t is more than 5 minutes away from your clock. Use the raw body bytes — parsing and re-serialising JSON changes them.

# Shell (OpenSSL) — for debugging a captured delivery. In production verify in your application code: this
# comparison is not constant-time.
# Usage: verify_signature "$X_LMS_SIGNATURE" body.json "$WEBHOOK_SECRET" [now]
verify_signature() {
  local header="$1" body_file="$2" secret="$3" now="${4:-$(date +%s)}"
  local timestamp="" expected part
  for part in ${header//,/ }; do
    case "$part" in t=*) timestamp="${part#t=}" ;; esac
  done
  case "$timestamp" in ''|*[!0-9]*) return 1 ;; esac
  local age=$((now - timestamp))
  [ "${age#-}" -le 300 ] || return 1
  expected=$( { printf '%s.' "$timestamp"; cat "$body_file"; } | openssl dgst -sha256 -hmac "$secret" -r | cut -d' ' -f1)
  for part in ${header//,/ }; do
    [ "$part" = "v1=$expected" ] && return 0
  done
  return 1
}

Test your implementation against the test vectors (valid, rotated, wrong secret, modified body, expired timestamp).

Delivery and retries

  • Respond with any 2xx within 5 seconds to acknowledge. Do the work asynchronously.
  • Otherwise the delivery is retried after 60 s, 5 min, 30 min and 2 h (5 attempts in total).
  • Delivery is at least once: deduplicate by the event id (X-LMS-Event-Id).
  • Order is not guaranteed; use created_at and the current state when it matters.
  • After 5 consecutive failures the subscription is degraded (deliveries continue); after 20 consecutive failures or 3 days of failures it is disabled and your IT administrators get an e-mail. Re-enable it with POST …/{id}/enable; pending deliveries are then sent.
  • GET …/{id}/deliveries shows the delivery log (metadata only, kept for 30 days); POST …/deliveries/{id}/retry retries one failed delivery.

Rotate the secret

POST /api/v1/webhooks/subscriptions/{id}/rotate-secret returns a new secret immediately. For grace_days (default 7, max 30) deliveries carry two v1 signatures — new and previous secret — so you can deploy the new secret without losing events.