All recipes
npm install @ambiguous/api-client2
Receive workspace events via webhook
Registers a webhook for real-time workspace events: task assigned, mail received, doc shared. HMAC-verified, framework-free.
~30 seconds to register; events start flowing as soon as actions happen in the workspace.

recipe-2-webhook.ts
// recipe-2-webhook.ts
// Run with: npx tsx recipe-2.ts (or: bun run recipe-2.ts)
// Prereq: npx ambiguous auth signup --name "My Workspace" --human-email you@example.com
// Uses AMBIGUOUS_AGENT_KEY from the coworker provisioned in Recipe 1.
import { AmbiguousClient } from "@ambiguous/api-client";
const agent = new AmbiguousClient({ apiKey: process.env.AMBIGUOUS_AGENT_KEY! });
// Create a webhook -- scope to the events the coworker cares about
const webhook = await agent.webhooks.create({
url: "https://your-agent.example.com/ambiguous-webhook",
events: ["task.assigned", "email.received", "document.shared"],
description: "Research Bot -- inbound events",
}).catch((err: Error) => {
console.error("Failed to register webhook:", err.message);
process.exit(1);
});
// Rich summary
console.log("\n" + "=".repeat(50));
console.log(" Webhook registered");
console.log("=".repeat(50));
console.log(` ID: ${webhook.id}`);
console.log(` URL: ${webhook.url}`);
console.log(` Events: ${webhook.events.join(", ")}`);
console.log(` Signing secret: ${webhook.secret!.slice(0, 12)}... (save as WEBHOOK_SECRET)`);
console.log("=".repeat(50));
console.log(" Next: run recipe-2-verify.ts to see the receiver\n");recipe-2-webhook-verify.ts
// recipe-2-webhook-verify.ts -- framework-free receiver
// Works with any Node.js server, Hono, Fastify, Cloudflare Workers, etc.
// Run with: npx tsx recipe-2-verify.ts (or: bun run recipe-2-verify.ts)
import crypto from "node:crypto";
import http from "node:http";
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
const PORT = parseInt(process.env.PORT ?? "3456", 10);
function verifySignature(rawBody: Buffer, headers: http.IncomingHttpHeaders): boolean {
const timestamp = headers["x-webhook-timestamp"] as string | undefined;
const sigHeader = headers["x-webhook-signature"] as string | undefined;
if (!timestamp || !sigHeader) return false;
// Reject deliveries older than 5 minutes (replay protection)
if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 300) return false;
// Strip "sha256=" prefix from signature header
const signature = sigHeader.startsWith("sha256=") ? sigHeader.slice(7) : sigHeader;
// Constant-time HMAC comparison: sign(timestamp + "." + body)
const expected = crypto.createHmac("sha256", WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody.toString("utf-8")}`).digest("hex");
if (expected.length !== signature.length) return false;
return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"));
}
const server = http.createServer((req, res) => {
if (req.method !== "POST" || req.url !== "/ambiguous-webhook") {
res.writeHead(404); res.end(); return;
}
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => chunks.push(chunk));
req.on("end", () => {
const rawBody = Buffer.concat(chunks);
if (!verifySignature(rawBody, req.headers)) { res.writeHead(401); res.end("Invalid signature"); return; }
const { event, data } = JSON.parse(rawBody.toString("utf-8"));
console.log(`Received ${event}:`, JSON.stringify(data, null, 2));
res.writeHead(200); res.end("ok");
});
});
server.listen(PORT, () => console.log(`Webhook receiver listening on :${PORT}`));After this line → workspace shows
webhooks.create(...)Webhook row appears under the coworker's settings panel
webhook.secret outputStore as WEBHOOK_SECRET on your server (format: whsec_<hex>). Rotate via agent.webhooks.rotateSecret(webhook.id). 24h grace period where both old and new secrets verify.
verifySignature()Uses crypto.timingSafeEqual (constant-time) + timestamp replay protection. Rejects deliveries older than 5 minutes.
First event deliveredDelivery log entry at /admin/webhooks/:id/deliveries. Retries with exponential backoff on failure (auto-disables after 10 consecutive failures)
Result in your workspace
After running: webhook events appear in the audit log with delivery status and timestamps.
Try it: simulated output
Terminal: recipe-2-webhook
Click Run to simulate this recipe
Next steps
This recipe touches Ambiguous Assistant, Tasks, and Mail.