Developer API · updated 2026-08-22

    Verify webhook signatures (HMAC)

    Every webhook delivery is signed so your server can prove it came from Dumpster Controls and was not tampered with. Verification is a dozen lines of code in any language. Never process an unverified webhook.

    1. 1

      The signature header

      Each delivery carries:

      X-DC-Signature: t=1755900000,v1=a1b2c3...

      t is the unix timestamp when we signed, and v1 is the hex HMAC-SHA256 of the string "<t>.<raw body>" computed with your endpoint secret (dcwh_...).

    2. 2

      Verification steps

      1. Read the RAW request body (before any JSON parsing; whitespace matters). 2. Parse t and v1 from the header. 3. Compute HMAC-SHA256(secret, t + "." + rawBody) and hex-encode it. 4. Compare with v1 using a constant-time comparison. 5. Reject if |now - t| is more than about 5 minutes (replay protection).

    3. 3

      Node.js example

      const crypto = require("crypto");
      function verify(rawBody, header, secret) {
        const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
        const expected = crypto.createHmac("sha256", secret)
          .update(parts.t + "." + rawBody).digest("hex");
        const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
        const sig = Buffer.from(parts.v1 || "");
        // timingSafeEqual THROWS on unequal lengths: guard first
        return fresh && sig.length === 64 &&
          crypto.timingSafeEqual(Buffer.from(expected), sig);
      }
    4. 4

      Python example

      import hmac, hashlib, time
      def verify(raw_body: bytes, header: str, secret: str) -> bool:
          parts = dict(p.split("=") for p in header.split(","))
          expected = hmac.new(secret.encode(),
              f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
          fresh = abs(time.time() - int(parts["t"])) < 300
          return fresh and hmac.compare_digest(expected, parts["v1"])
    5. 5

      Common mistakes

      Parsing the JSON and re-serializing it before hashing (key order changes, verification fails): always hash the raw bytes. Using == instead of a constant-time compare. Skipping the timestamp check, which allows replays of old captured deliveries.

    Related guides

    Still stuck? Talk to our team

    Open a support ticket inside the app and our team will answer there.

    Log in and open a support ticket

    Try it yourself, free

    The software is 100% free with unlimited orders. No credit card, no demo, no contract.

    Create your free account