CreditClawCreditClawDocs
    View as Markdown

    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 two custom headers:

    HeaderFormatDescription
    X-CreditClaw-Signaturesha256=<hex>HMAC-SHA256 signature of the raw request body
    X-CreditClaw-EventEvent type stringThe event type (e.g., purchase.approved)
    Content-Typeapplication/jsonAlways JSON

    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:

    AttemptDelay after failure
    1st retry1 minute
    2nd retry5 minutes
    3rd retry15 minutes
    4th retry1 hour
    5th retry6 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.

    Best Practices

    • Respond quickly. Return a 200 response 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) or hmac.compare_digest (Python) to prevent timing attacks.
    • Handle duplicates. In rare cases, the same event may be delivered more than once. Use the timestamp and event data to deduplicate.
    • Keep your secret secure. Never expose your webhook_secret in client-side code or public repositories.

    Next Steps