Agent skill

Coassemble Webhooks Skill

Receive and verify Coassemble webhooks. Use when setting up Coassemble course and learner webhook handlers, debugging X-Coassemble-Signature verification, or handling events like course.completed, course.commenced, quiz.completed, quiz.failed, and migration.completed.

Install this skill

npx skills add hookdeck/webhook-skills --skill coassemble-webhooks


When to Use This Skill

  • How do I receive Coassemble webhooks?
  • How do I verify the X-Coassemble-Signature header?
  • How do I handle course.completed, quiz.failed, or migration.completed events?
  • Why is my Coassemble webhook signature verification failing?
  • How do I sync Coassemble learner completions to my LMS, HRIS or CRM?

Essential Code (USE THIS)

Coassemble signs every delivery with two headers:

X-Coassemble-Signature: sha256=<hex>
X-Coassemble-Timestamp: 1736937000
  • The signature is HMAC-SHA256 (lowercase hex) of <X-Coassemble-Timestamp value>.<raw body>, keyed with the endpoint's signing secret (returned once when you create the endpoint), used as-is (UTF-8, not decoded).
  • Accept the digest with or without the sha256= prefix, and compare hex case-insensitively.
  • The event name is in the body's type field (also sent in X-Coassemble-Event). X-Coassemble-Delivery is the same across retries and replays, so use it for idempotency.
  • Test deliveries have testMode: true and are signed like real ones; verify, return 2xx, then skip production actions.
  • Answer a missing, malformed or wrong signature with 401.

Verification (Node.js)

const crypto = require('crypto');

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyCoassemble(rawBody, signatureHeader, timestampHeader, secret) {
  if (!secret || !signatureHeader || !timestampHeader) return false;
  const hex = signatureHeader.startsWith('sha256=') ? signatureHeader.slice(7) : signatureHeader;
  if (!/^[0-9a-fA-F]{64}$/.test(hex)) return false; // validate before decoding
  const message = Buffer.concat([Buffer.from(timestampHeader, 'latin1'), Buffer.from('.'), rawBody]);
  const expected = crypto.createHmac('sha256', secret).update(message).digest();
  const received = Buffer.from(hex, 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

Verification (Python)

import hashlib, hmac, re

def verify_coassemble(raw_body: bytes, signature_header, timestamp_header, secret: str) -> bool:
    if not secret or not signature_header or not timestamp_header:
        return False
    digest = signature_header.removeprefix("sha256=")
    if not re.fullmatch(r"[0-9a-fA-F]{64}", digest):  # validate before decoding
        return False
    message = b"".join([timestamp_header.encode("latin-1"), b".", raw_body])
    expected = hmac.new(secret.encode(), message, hashlib.sha256).digest()
    return hmac.compare_digest(bytes.fromhex(digest), expected)

Coassemble's docs show a Node sample that interpolates a string body; the examples sign the raw body bytes instead, since re-serialized JSON can differ from what was sent.

The timestamp age check is off by default, because retries and replays (Coassemble's and Hookdeck's) resend the original X-Coassemble-Timestamp. Set COASSEMBLE_WEBHOOK_TOLERANCE_SECONDS to a positive number of seconds (for example 300) to opt in. Use X-Coassemble-Delivery for idempotency instead.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

Dispatch on the body's type field.

EventTriggered When
course.createdA new course is created
course.commencedA learner first commences a course tracking record
course.completedA learner first completes a course tracking record
quiz.completedA learner passes a standalone Screen Player quiz
quiz.failedA learner fails a standalone Screen Player quiz
migration.progressOne course in a Classic migration batch finishes
migration.completedA Classic migration batch reaches completed or completed_with_errors

For payload shapes and the full list, see references/overview.md and Coassemble's webhook docs.

Important Headers

HeaderDescription
X-Coassemble-Signaturesha256=<hex> HMAC-SHA256 signature
X-Coassemble-TimestampUnix timestamp (seconds) used for signature verification
X-Coassemble-EventThe event type
X-Coassemble-DeliveryUnique delivery ID (UUID), stable across retries
X-Coassemble-Test-Modetrue or false, matching the body's testMode

Environment Variables

COASSEMBLE_WEBHOOK_SECRET=your_webhook_secret   # returned once when the endpoint is created
COASSEMBLE_WEBHOOK_TOLERANCE_SECONDS=0          # optional; a positive value opts in to the replay window

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 coassemble --path /webhooks/coassemble

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →