API Cookbook — Webhook Patterns

Note on signature headers: LEAST uses two webhook systems with different headers. Collection API webhooks use X-LEAST-Signature. Workflow Webhooks and LMS Rule Engine webhooks use X-WF-Signature. The patterns below apply to both — substitute the correct header name.

Receiving a Webhook (PHP)

<?php
// webhook-receiver.php — Collection API webhook
$raw       = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_LEAST_SIGNATURE"] ?? "";  // Collection API
// For Workflow / LMS Rule webhooks: $_SERVER["HTTP_X_WF_SIGNATURE"]
$secret    = "your-webhook-secret";
 
$expected = "sha256=" . hash_hmac("sha256", $raw, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit("Invalid signature");
}
 
http_response_code(200);
echo "ok";
 
$payload = json_decode($raw, true);
file_put_contents("/tmp/webhook_queue.jsonl",
    json_encode($payload) . "\n", FILE_APPEND);

Minimal Node.js Receiver

const express = require("express");
const crypto  = require("crypto");
const app     = express();
 
app.use(express.raw({ type: "application/json" }));
 
app.post("/webhook", (req, res) => {
    const secret    = "your-webhook-secret";
    // Collection API: "x-least-signature" | Workflow/LMS: "x-wf-signature"
    const signature = req.headers["x-least-signature"];
    const expected  = "sha256=" + crypto.createHmac("sha256", secret)
                                        .update(req.body).digest("hex");
    if (signature !== expected) return res.status(401).send("Bad signature");
 
    const payload = JSON.parse(req.body);
    console.log("Event:", payload.event);
    res.status(200).send("ok");
});
 
app.listen(3000);

Routing Collection API Events

switch ($payload["event"]) {
    case "record.created":
        notify_slack("New item: " . $payload["data"]["strD5_1_1"]);
        break;
    case "record.action":
        if ($payload["action_id"] === 8) { // Action 8 = Complete
            close_external_ticket($payload["record_id"]);
        }
        break;
    case "record.archived":
        archive_in_crm($payload["record_id"]);
        break;
}

Routing Workflow / LMS Rule Events

For Workflow Webhooks (header: X-WF-Signature):

switch ($payload["event"]) {
    case "lesson.completed":
        update_sis_progress($payload["intLessonId"]);
        break;
    case "badge.earned":
        post_to_linkedin($payload["intBadgeId"]);
        break;
    case "workflow.completed":
        notify_hr_system($payload["intInstanceId"]);
        break;
}

Zapier / Make (Integromat)

Both Zapier and Make support a “Webhooks” trigger:

  1. Create a Zap / Scenario → add a Webhook trigger → copy the generated URL
  2. In LEAST: collection admin → Webhooks → add the URL; or Admin → Workflow Webhooks for learner events
  3. Trigger a test event — Zapier / Make catches it — build downstream steps

Signature verification is not supported natively in Zapier's free tier. Use a Code step to verify manually.

See Also