meta data for this page
  •  

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

developer:cookbook-webhooks [2026/05/18 07:38] – Initial content claudedeveloper:cookbook-webhooks [2026/05/23 18:04] (current) – Add X-WF-Signature note, Workflow/LMS event routing example, two-system summary claude
Line 1: Line 1:
 ====== API Cookbook — Webhook Patterns ====== ====== API Cookbook — Webhook Patterns ======
  
-===== Receiving a Webhook =====+> **Note on signature headers:** LEAST uses two webhook systems with different headers. Collection API webhooks use ''X-LEAST-Signature''. [[developer:webhooks#part_2_workflow_webhooks|Workflow Webhooks]] and LMS Rule Engine webhooks use ''X-WF-Signature''. The patterns below apply to both — substitute the correct header name.
  
-LEAST sends POST request to your endpoint with ''Content-Type: application/json''. Respond with HTTP 200 within 10 seconds to acknowledge receipt. Process asynchronously if your handler takes longer. +===== Receiving Webhook (PHP) =====
- +
-Minimal PHP receiver:+
  
 <code php> <code php>
 <?php <?php
-// webhook-receiver.php +// webhook-receiver.php — Collection API webhook 
-$payload   json_decode(file_get_contents('php://input'), true); +$raw       = file_get_contents("php://input"); 
-$signature = $_SERVER['HTTP_X_LEAST_SIGNATURE'] ?? ''+$signature = $_SERVER["HTTP_X_LEAST_SIGNATURE"] ?? "" // Collection API 
-$secret    = 'your-webhook-secret';+// For Workflow / LMS Rule webhooks: $_SERVER["HTTP_X_WF_SIGNATURE"] 
 +$secret    = "your-webhook-secret";
  
-// Verify signature +$expected = "sha256=. hash_hmac("sha256"$raw, $secret);
-$expected = 'sha256=. hash_hmac('sha256'file_get_contents('php://input'), $secret);+
 if (!hash_equals($expected, $signature)) { if (!hash_equals($expected, $signature)) {
     http_response_code(401);     http_response_code(401);
-    exit('Invalid signature');+    exit("Invalid signature");
 } }
  
-// Acknowledge immediately 
 http_response_code(200); http_response_code(200);
-echo 'ok';+echo "ok";
  
-// Process asynchronously (queue the payloaddon't block here+$payload = json_decode($rawtrue); 
-file_put_contents('/tmp/webhook_queue.jsonl',+file_put_contents("/tmp/webhook_queue.jsonl",
     json_encode($payload) . "\n", FILE_APPEND);     json_encode($payload) . "\n", FILE_APPEND);
 </code> </code>
Line 33: Line 30:
  
 <code javascript> <code javascript>
-const express = require('express'); +const express = require("express"); 
-const crypto  = require('crypto');+const crypto  = require("crypto");
 const app     = express(); const app     = express();
  
-app.use(express.raw({ type: 'application/json}));+app.use(express.raw({ type: "application/json}));
  
-app.post('/webhook', (req, res) => { +app.post("/webhook", (req, res) => { 
-    const secret    = 'your-webhook-secret'+    const secret    = "your-webhook-secret"; 
-    const signature = req.headers['x-least-signature']; +    // Collection API: "x-least-signature" | Workflow/LMS: "x-wf-signature" 
-    const expected 'sha256=+ crypto.createHmac('sha256', secret) +    const signature = req.headers["x-least-signature"]; 
-                                        .update(req.body).digest('hex'); +    const expected "sha256=+ crypto.createHmac("sha256", secret) 
- +                                        .update(req.body).digest("hex"); 
-    if (signature !== expected) return res.status(401).send('Bad signature');+    if (signature !== expected) return res.status(401).send("Bad signature");
  
     const payload = JSON.parse(req.body);     const payload = JSON.parse(req.body);
-    console.log('Event:', payload.event, 'Record:', payload.record_id); +    console.log("Event:", payload.event); 
- +    res.status(200).send("ok");
-    res.status(200).send('ok'); +
-    // process payload asynchronously...+
 }); });
  
Line 57: Line 52:
 </code> </code>
  
-===== Routing Events by Type =====+===== Routing Collection API Events =====
  
 <code php> <code php>
-switch ($payload['event']) { +switch ($payload["event"]) { 
-    case 'record.created': +    case "record.created"
-        // New record — send a Slack notification, create a Jira ticket, etc. +        notify_slack("New item: " . $payload["data"]["strD5_1_1"]);
-        notify_slack("New item: " . $payload['data']['strD5_1_1']);+
         break;         break;
-    case 'record.action': +    case "record.action"
-        // Status change — check which action was taken +        if ($payload["action_id"] === 8) { // Action 8 = Complete 
-        if ($payload['action_id'] === 8) { // Action 8 = Complete +            close_external_ticket($payload["record_id"]);
-            close_external_ticket($payload['record_id']);+
         }         }
         break;         break;
-    case 'record.archived'+    case "record.archived"
-        archive_in_crm($payload['record_id']);+        archive_in_crm($payload["record_id"]); 
 +        break; 
 +
 +</code> 
 + 
 +===== Routing Workflow / LMS Rule Events ===== 
 + 
 +For [[developer:webhooks#part_2_workflow_webhooks|Workflow Webhooks]] (header: ''X-WF-Signature''): 
 + 
 +<code php> 
 +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;         break;
 } }
Line 79: Line 90:
 ===== Zapier / Make (Integromat) ===== ===== Zapier / Make (Integromat) =====
  
-Both Zapier and Make have a "Webhooks" trigger that can receive LEAST webhook deliveries+Both Zapier and Make support a "Webhooks" trigger: 
-  - Create a new Zap / Scenario +  - Create a Zap / Scenario → add a **Webhook** trigger → copy the generated URL 
-  - Add a **Webhook** trigger → copy the generated URL +  - In LEASTcollection admin → Webhooks → add the URL; or Admin → Workflow Webhooks for learner events 
-  - In LEAST collection admin → Webhooks → add the Zapier/Make URL as the target +  - Trigger a test event — Zapier / Make catches it — build downstream steps 
-  - Trigger a test event in LEAST → the webhook fires → Zapier/Make catches it + 
-  - Build downstream steps: create Google Sheets row, send a Slack message, create a HubSpot deal, etc.+Signature verification is not supported natively in Zapier's free tier. Use Code step to verify manually. 
 + 
 +===== See Also =====
  
-Signature verification in Zapiernot natively supported in the free tier. Use a Code step to verify manually if security is required.+  * [[developer:webhooks|Webhooks]] — full reference for both systems 
 +  * [[developer:lms-rules|LMS Automation Rules]] — event-driven rule engine