meta data for this page
Differences
This shows you the differences between two versions of the page.
| developer:cookbook-webhooks [2026/05/18 07:38] – Initial content claude | developer: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 '' |
| - | LEAST sends a POST request to your endpoint with '' | + | ===== Receiving |
| - | + | ||
| - | Minimal | + | |
| <code php> | <code php> | ||
| <?php | <?php | ||
| - | // webhook-receiver.php | + | // webhook-receiver.php |
| - | $payload | + | $raw = file_get_contents("php://input"); |
| - | $signature = $_SERVER['HTTP_X_LEAST_SIGNATURE'] ?? '' | + | $signature = $_SERVER["HTTP_X_LEAST_SIGNATURE"] ?? "" |
| - | $secret | + | // For Workflow / LMS Rule webhooks: $_SERVER[" |
| + | $secret | ||
| - | // Verify signature | + | $expected = "sha256=" |
| - | $expected = 'sha256=' | + | |
| if (!hash_equals($expected, | if (!hash_equals($expected, | ||
| 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 | + | $payload = json_decode($raw, true); |
| - | file_put_contents('/ | + | file_put_contents("/ |
| json_encode($payload) . " | json_encode($payload) . " | ||
| </ | </ | ||
| Line 33: | Line 30: | ||
| <code javascript> | <code javascript> | ||
| - | const express = require('express'); | + | const express = require("express"); |
| - | const crypto | + | const crypto |
| const app = express(); | const app = express(); | ||
| - | app.use(express.raw({ type: 'application/ | + | app.use(express.raw({ type: "application/ |
| - | app.post('/webhook', (req, res) => { | + | app.post("/webhook", (req, res) => { |
| - | const secret | + | const secret |
| - | const signature = req.headers['x-least-signature']; | + | // Collection API: " |
| - | const expected | + | const signature = req.headers["x-least-signature"]; |
| - | .update(req.body).digest('hex'); | + | const expected |
| - | + | .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, ' | + | console.log("Event:", payload.event); |
| - | + | res.status(200).send("ok"); | |
| - | res.status(200).send('ok'); | + | |
| - | // process payload asynchronously... | + | |
| }); | }); | ||
| Line 57: | Line 52: | ||
| </ | </ | ||
| - | ===== Routing Events | + | ===== Routing |
| <code php> | <code php> | ||
| - | switch ($payload['event']) { | + | switch ($payload["event"]) { |
| - | case 'record.created': | + | case "record.created": |
| - | // New record — send a Slack notification, | + | notify_slack(" |
| - | notify_slack(" | + | |
| 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; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Routing Workflow / LMS Rule Events ===== | ||
| + | |||
| + | For [[developer: | ||
| + | |||
| + | <code php> | ||
| + | switch ($payload[" | ||
| + | case " | ||
| + | update_sis_progress($payload[" | ||
| + | break; | ||
| + | case " | ||
| + | post_to_linkedin($payload[" | ||
| + | break; | ||
| + | case " | ||
| + | notify_hr_system($payload[" | ||
| break; | break; | ||
| } | } | ||
| Line 79: | Line 90: | ||
| ===== Zapier / Make (Integromat) ===== | ===== Zapier / Make (Integromat) ===== | ||
| - | Both Zapier and Make have a " | + | Both Zapier and Make support |
| - | - Create a new Zap / Scenario | + | - Create a Zap / Scenario |
| - | - Add a **Webhook** trigger → copy the generated URL | + | - In LEAST: collection admin → Webhooks → add the URL; or Admin → Workflow Webhooks for learner events |
| - | - In LEAST collection admin → Webhooks → add the Zapier/ | + | - 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 | + | Signature verification is not supported natively in Zapier' |
| + | |||
| + | ===== See Also ===== | ||
| - | Signature verification in Zapier: not 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: | ||