Agent skill

Doppler Webhooks Skill

Receive and verify Doppler webhooks. Use when setting up Doppler webhook handlers, debugging X-Doppler-Signature verification, or handling config.secrets.update events when secrets change in a project config.

Install this skill

npx skills add hookdeck/webhook-skills --skill doppler-webhooks


When to Use This Skill

  • How do I receive Doppler webhooks?
  • How do I verify the X-Doppler-Signature header?
  • How do I handle config.secrets.update events (restart or redeploy when secrets change)?
  • Why is my Doppler webhook signature verification failing?
  • How do I verify a custom JSON payload from Doppler?

Verification (core)

Doppler signing is optional: it only happens if you enter a Secret on the webhook, so always set one. The X-Doppler-Signature header is sha256=<hex>, the HMAC-SHA256 of the raw request body keyed on that Secret. Custom JSON payloads are signed the same way.

Do not copy the docs' Node sample, which hashes JSON.stringify(req.body). Re-serialized JSON can differ from the bytes Doppler signed. Hash the raw bytes and compare timing-safe.

Node:

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python:

import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header, expected)

In Express use express.raw({ type: '*/*' }) so the body stays a Buffer whatever the content type.

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types

Event (type field)Description
config.secrets.updateSecrets changed in a config. The only documented type; the default payload always carries it

The default payload has type, config, project, workplace, and diff (added, removed, updated secret names). A custom JSON payload replaces all of this and has no type, so don't assume one.

Important Headers

HeaderDescription
X-Doppler-Signaturesha256=<hex> HMAC-SHA256 of the raw body. Only sent when a Secret is set

Environment Variables

DOPPLER_WEBHOOK_SECRET=your_webhook_secret   # The "Secret" field on the Doppler webhook

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 doppler --path /webhooks/doppler

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →