Guide
Setting Up Webhooks
Receive real-time events from ClawdGo and wire them into your alerting, logging, and application logic.
Register a webhook
typescript
const webhook = await client.webhooks.create({
url: "https://your-service.example.com/clawdgo/events",
events: [
"transfer.settled",
"transfer.rejected",
"balance.low_threshold",
],
low_balance_threshold_usdc: 50, // alert when balance drops below $50
});
console.log("Webhook secret:", webhook.secret);
// Store this — you'll use it to verify incoming payloads
Handle incoming events
A minimal Express handler that verifies signatures and processes events:
typescript
import express from "express";
import crypto from "node:crypto";
const app = express();
app.use("/clawdgo/events", express.raw({ type: "application/json" }));
app.post("/clawdgo/events", (req, res) => {
const signature = req.headers["x-clawdgo-signature"] as string;
const payload = req.body.toString();
// 1. Verify signature first — reject anything that doesn't match
const expected = crypto
.createHmac("sha256", process.env.CLAWDGO_WEBHOOK_SECRET)
.update(payload)
.digest("hex");
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
if (!isValid) {
return res.status(401).send("Invalid signature");
}
// 2. Respond 200 immediately — process async to avoid timeouts
res.sendStatus(200);
// 3. Process event asynchronously
processEvent(JSON.parse(payload)).catch(console.error);
});
async function processEvent(event: any) {
switch (event.type) {
case "transfer.settled":
await onTransferSettled(event.data);
break;
case "transfer.rejected":
await onTransferRejected(event.data);
break;
case "balance.low_threshold":
await alertLowBalance(event.data);
break;
}
}
Event handlers
transfer.rejected — alert on policy violations
typescript
async function onTransferRejected(data: any) {
// Log to your incident system
console.error(`Policy violation on account ${data.account_id}:`, data.rejection_reason);
// If it's unexpected — alert the team
if (!isExpectedRejection(data)) {
await pagerduty.trigger({
title: `ClawdGo policy violation: ${data.account_id}`,
body: data.rejection_reason,
});
}
}
balance.low_threshold — trigger a top-up
typescript
async function alertLowBalance(data: any) {
// Option 1: alert a human to top up manually
await slack.post({
channel: "#ops-alerts",
text: `Agent account ${data.account_id} balance is $${data.balance_usdc} USDC. Please top up.`,
});
// Option 2: auto top-up via Circle API (if Circle integration is configured)
if (AUTO_TOPUP_ENABLED) {
await circle.transfer({
destination: data.usdc_token_account,
amount: TOP_UP_AMOUNT,
});
}
}
Testing webhooks locally
Use a tunneling tool like ngrok or cloudflared to expose a local server during development:
bash
ngrok http 3000
# → https://abc123.ngrok.io
# Register the tunnel URL as your webhook endpoint:
curl -X POST https://api.clawdgo.com/v1/webhooks \
-H "Authorization: Bearer $CLAWDGO_API_KEY" \
-d '{"url": "https://abc123.ngrok.io/clawdgo/events", "events": ["transfer.settled"]}'