Webhook Events Reference
Complete reference for all webhook events sent by the SCORM API.
Table of Contents
Event Overview
All webhook events follow a consistent structure:
{
"event": "event.type.name",
"tenant_id": "tnt_your_tenant",
"data": {
"audit_event_id": "evt_abc123",
"event_plane": null,
"entity_type": "session",
"entity_id": "session_abc123",
"severity": "info"
},
"timestamp": "2025-01-15T10:30:00.000Z",
"delivery_id": "whd_evt_abc123_endpoint1"
}
Common Fields
- event (string): Event type identifier
- tenant_id (string): Tenant that triggered the event
- data (object): Event-specific payload
- timestamp (ISO 8601): When the event occurred
- delivery_id (string): Unique delivery identifier for tracking
Every data object carries these baseline fields, drawn from the underlying
audit event:
- audit_event_id (string): The source audit event
- event_plane (string | null): Event plane, when set
- entity_type (string | null): Related entity type, when set
- entity_id (string | null): Related entity id, when set
- severity (string):
infoorwarn
Session events additionally include the session detail fields shown below.
For session events, data.updated_at is the session detail timestamp. The
top-level timestamp is the source webhook/audit event timestamp.
There is no
session.progressevent and no per-commit event. In-progress commits are not pushed by design — pollGET /api/v1/sessions/{sessionId}for live progress on an active session.
Event Types
An endpoint subscribes to a scope (packages, sessions, usage, or
all) and receives every delivered event in that scope.
Package Events (scope: packages)
package.launched— a package is launched for a learnerpackage.archived— a package is archivedpackage.version_activated— a new package version becomes active
Payload:
{
"event": "package.launched",
"tenant_id": "tnt_your_tenant",
"data": {
"audit_event_id": "evt_abc123",
"event_plane": null,
"entity_type": "package",
"entity_id": "pkg_abc123",
"severity": "info"
},
"timestamp": "2026-07-06T10:30:00.000Z",
"delivery_id": "whd_evt_abc123_endpoint1"
}
Session Events (scope: sessions)
session.completed— a session reachescompleted/passedsession.failed— a session ends in a failed statesession.suspended— a session is suspended (learner exits mid-course)
Session events carry the baseline fields plus session detail:
Payload:
{
"event": "session.completed",
"tenant_id": "tnt_your_tenant",
"data": {
"audit_event_id": "evt_abc123",
"event_plane": null,
"entity_type": "session",
"entity_id": "session_abc123",
"severity": "info",
"session_id": "session_abc123",
"package_id": "pkg_abc123",
"learner_id": "user_xyz789",
"completion_status": "completed",
"success_status": "passed",
"progress_percent": 100,
"score": {
"scaled": 0.85,
"raw": 85,
"min": 0,
"max": 100
},
"session_time": "PT1H0M0S",
"time_spent_seconds": 3600,
"updated_at": "2026-07-06T10:29:45.000Z"
},
"timestamp": "2026-07-06T10:30:00.000Z",
"delivery_id": "whd_evt_abc123_endpoint1"
}
Use Cases:
- Issue completion certificates and update grade books
- Trigger follow-up actions on failed or suspended sessions
- Send completion notifications and award badges
Usage Events (scope: usage)
usage.threshold_reached.70— usage crosses 70% of the active-learner allotmentusage.threshold_reached.90— usage crosses 90%usage.threshold_reached.100— usage reaches 100%
Each threshold fires once per billing period, on the launch that moves the distinct active-learner count from below the threshold to at-or-above it.
Payload:
{
"event": "usage.threshold_reached.90",
"tenant_id": "tnt_your_tenant",
"data": {
"audit_event_id": "evt_abc123",
"event_plane": null,
"entity_type": null,
"entity_id": null,
"severity": "info"
},
"timestamp": "2026-07-06T10:30:00.000Z",
"delivery_id": "whd_evt_abc123_endpoint1"
}
Delivery Guarantees
At-Least-Once Delivery
The SCORM API guarantees at-least-once delivery:
- Events are delivered at least once
- Events may be delivered multiple times (duplicates)
- Failed deliveries are retried automatically
Retry Schedule
Failed deliveries are retried with increasing backoff between attempts:
| Attempt | Backoff before this attempt |
|---|---|
| 1 | Immediate |
| 2 | 60s |
| 3 | 5m |
| 4 | 15m |
| 5 | 60m |
A delivery gets up to 5 attempts. After the 5th failed attempt it is moved
to the dead-letter state. Terminal responses (404/410) dead-letter
immediately without further retries, since re-sending won't reach a missing
endpoint.
Delivery Status
- queued: Waiting to be delivered (or waiting for its next retry)
- delivered: Successfully delivered (2xx response)
- failed: Last attempt failed; will be retried if attempts remain
- dead_letter: Max attempts exhausted, or a terminal/undeliverable response
Retry Logic
Automatic Retries
Connect automatically retries failed deliveries:
- Increasing backoff between attempts (60s → 5m → 15m → 60m)
- Up to 5 attempts, then dead-lettered
- 30-second timeout per attempt
Manual Retry
You can manually re-queue a failed or dead-lettered delivery:
curl -X POST https://app.allureconnect.com/api/customer/webhooks/deliveries/delivery_xyz789/retry \
-H "X-API-Key: your-api-key"
Idempotency
Handling Duplicate Events
Since webhooks use at-least-once delivery, you may receive duplicate events. Implement idempotency:
const processedDeliveries = new Set<string>();
export async function POST(request: Request) {
const event = await request.json();
const deliveryId = event.delivery_id;
// Check if already processed
if (processedDeliveries.has(deliveryId)) {
return new Response('Already processed', { status: 200 });
}
// Process event
await handleEvent(event);
// Mark as processed
processedDeliveries.add(deliveryId);
return new Response('OK', { status: 200 });
}
Database-Based Idempotency
For production, use database to track processed deliveries:
async function isDeliveryProcessed(deliveryId: string): Promise<boolean> {
const result = await db.webhookDeliveries.findUnique({
where: { delivery_id: deliveryId },
});
return !!result;
}
async function markDeliveryProcessed(deliveryId: string) {
await db.webhookDeliveries.create({
data: { delivery_id: deliveryId, processed_at: new Date() },
});
}
Best Practices
- Process Asynchronously: Return 200 immediately, process in background
- Implement Idempotency: Handle duplicate deliveries gracefully
- Log All Events: Keep audit trail of all webhook events
- Handle Errors: Don't let webhook processing crash your application
- Monitor Delivery: Track delivery success rates
- Poll for Live Progress: There is no per-commit progress event — poll
GET /api/v1/sessions/{sessionId}when you need up-to-the-moment progress
Related Documentation
- Webhook Setup - How to set up webhooks
- Webhook Security - Security best practices
- API Reference - Complete API documentation
Last Updated: 2026-07-07