Webhooks Setup & Signing
Webhooks let CreditClaw push real-time event notifications to your bot instead of requiring it to poll for updates. When something happens — a purchase is approved, a wallet is funded, an order ships — CreditClaw sends an HTTP POST to your bot's registered callback_url.
Webhook Secret
When you register a bot via POST /api/v1/bots/register, the response includes a webhook_secret with the prefix whsec_:
{
"api_key": "cck_live_...",
"webhook_secret": "whsec_a1b2c3d4e5f6..."
}
Store this secret securely. It is used to verify that incoming webhook requests genuinely originate from CreditClaw.
Delivery
CreditClaw delivers webhooks as POST requests to your bot's callback_url with a JSON body:
{
"event": "purchase.approved",
"timestamp": "2025-01-15T10:30:00.000Z",
"bot_id": "bot_a1b2c3d4",
"data": {
"checkout_id": "chk_abc123",
"amount": 29.99,
"vendor": "example.com",
"description": "Monthly subscription"
}
}
Headers
Every webhook request includes these headers:
| Header | Format | Description |
|---|---|---|
X-CreditClaw-Signature | sha256=<hex> | HMAC-SHA256 signature of the raw request body |
X-CreditClaw-Event | Event type string | The event type (e.g., purchase.approved) |
Content-Type | application/json | Always JSON |
Authorization | Bearer <token> | Only sent to OpenClaw bots with a managed tunnel. Used by the OpenClaw Gateway to authenticate incoming webhooks. |
Verifying Signatures
Always verify the X-CreditClaw-Signature header before processing a webhook. This prevents spoofed requests.
The signature is computed as sha256= followed by the hex-encoded HMAC-SHA256 digest of the raw request body, using your webhook_secret as the key.
JavaScript (Node.js)
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhook(rawBody, signatureHeader, secret) {
const expectedSig = createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const expected = `sha256=${expectedSig}`;
if (expected.length !== signatureHeader.length) return false;
return timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-creditclaw-signature"];
const event = req.headers["x-creditclaw-event"];
if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(req.body);
console.log(`Received ${event}:`, payload.data);
res.status(200).send("OK");
});
Python
import hmac
import hashlib
def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected_sig = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
expected = f"sha256={expected_sig}"
return hmac.compare_digest(expected, signature_header)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
signature = request.headers.get("X-CreditClaw-Signature", "")
event = request.headers.get("X-CreditClaw-Event", "")
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
abort(401)
payload = request.get_json()
print(f"Received {event}: {payload['data']}")
return "OK", 200
Retry Behavior
If your endpoint does not return a 2xx status code (or the request times out after 10 seconds), CreditClaw will retry delivery with exponential backoff:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
| 4th retry | 1 hour |
| 5th retry | 6 hours |
After 5 failed attempts, the delivery is marked as failed and will not be retried automatically. You can view failed deliveries and trigger manual retries from the dashboard webhook log.
Managed Tunnels
If your bot doesn't have its own publicly accessible endpoint, CreditClaw can provision a managed Cloudflare tunnel at registration. Register without a callback_url and CreditClaw will create a permanent *.nortonbot.com webhook URL for your bot.
See the Managed Tunnels guide for the full setup walkthrough, including running cloudflared and configuring the OpenClaw Gateway.
Best Practices
- Respond quickly. Return a
200response as soon as you receive the webhook. Process the event asynchronously if your handler does heavy work. - Use timing-safe comparison. Always use
timingSafeEqual(Node.js) orhmac.compare_digest(Python) to prevent timing attacks. - Handle duplicates. In rare cases, the same event may be delivered more than once. Use the
timestampand event data to deduplicate. - Keep your secret secure. Never expose your
webhook_secretin client-side code or public repositories.
Next Steps
- Webhook Event Types — full reference of all event types and their payloads
- Health & Reliability — how CreditClaw tracks webhook health and falls back to pending messages
- Authentication — how API keys and webhook secrets are generated
- Quick Start — end-to-end guide from registration to first purchase